@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.cjs
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var axios = require('axios');
|
|
4
3
|
var zod = require('zod');
|
|
5
4
|
var crypto = require('crypto');
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __defProps = Object.defineProperties;
|
|
8
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
9
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
11
25
|
var __async = (__this, __arguments, generator) => {
|
|
12
26
|
return new Promise((resolve, reject) => {
|
|
13
27
|
var fulfilled = (value) => {
|
|
@@ -29,6 +43,231 @@ var __async = (__this, __arguments, generator) => {
|
|
|
29
43
|
});
|
|
30
44
|
};
|
|
31
45
|
|
|
46
|
+
// src/cache/cache-control-parser.ts
|
|
47
|
+
var EMPTY_DIRECTIVES = {
|
|
48
|
+
noCache: false,
|
|
49
|
+
noStore: false,
|
|
50
|
+
mustRevalidate: false,
|
|
51
|
+
proxyRevalidate: false,
|
|
52
|
+
public: false,
|
|
53
|
+
private: false,
|
|
54
|
+
immutable: false
|
|
55
|
+
};
|
|
56
|
+
function parseSeconds(raw) {
|
|
57
|
+
if (raw === void 0) return void 0;
|
|
58
|
+
const n = Number.parseInt(raw.trim(), 10);
|
|
59
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
60
|
+
}
|
|
61
|
+
function parseCacheControl(header) {
|
|
62
|
+
if (!header) return __spreadValues({}, EMPTY_DIRECTIVES);
|
|
63
|
+
const result = __spreadValues({}, EMPTY_DIRECTIVES);
|
|
64
|
+
for (const part of header.split(",")) {
|
|
65
|
+
const trimmed = part.trim();
|
|
66
|
+
if (!trimmed) continue;
|
|
67
|
+
const eqIdx = trimmed.indexOf("=");
|
|
68
|
+
const key = (eqIdx === -1 ? trimmed : trimmed.slice(0, eqIdx)).trim().toLowerCase();
|
|
69
|
+
const value = eqIdx === -1 ? void 0 : trimmed.slice(eqIdx + 1).trim();
|
|
70
|
+
switch (key) {
|
|
71
|
+
case "max-age":
|
|
72
|
+
result.maxAge = parseSeconds(value);
|
|
73
|
+
break;
|
|
74
|
+
case "s-maxage":
|
|
75
|
+
result.sMaxAge = parseSeconds(value);
|
|
76
|
+
break;
|
|
77
|
+
case "no-cache":
|
|
78
|
+
result.noCache = true;
|
|
79
|
+
break;
|
|
80
|
+
case "no-store":
|
|
81
|
+
result.noStore = true;
|
|
82
|
+
break;
|
|
83
|
+
case "must-revalidate":
|
|
84
|
+
result.mustRevalidate = true;
|
|
85
|
+
break;
|
|
86
|
+
case "proxy-revalidate":
|
|
87
|
+
result.proxyRevalidate = true;
|
|
88
|
+
break;
|
|
89
|
+
case "public":
|
|
90
|
+
result.public = true;
|
|
91
|
+
break;
|
|
92
|
+
case "private":
|
|
93
|
+
result.private = true;
|
|
94
|
+
break;
|
|
95
|
+
case "immutable":
|
|
96
|
+
result.immutable = true;
|
|
97
|
+
break;
|
|
98
|
+
case "stale-while-revalidate":
|
|
99
|
+
result.staleWhileRevalidate = parseSeconds(value);
|
|
100
|
+
break;
|
|
101
|
+
case "stale-if-error":
|
|
102
|
+
result.staleIfError = parseSeconds(value);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/cache/cache-entry.ts
|
|
110
|
+
function isCacheEntry(value) {
|
|
111
|
+
return typeof value === "object" && value !== null && value.__cacheEntry === true && "value" in value && "metadata" in value;
|
|
112
|
+
}
|
|
113
|
+
function parseHttpDate(value) {
|
|
114
|
+
if (value === null || value === void 0) return void 0;
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
if (!trimmed) return void 0;
|
|
117
|
+
if (trimmed === "0") return 0;
|
|
118
|
+
const ms = Date.parse(trimmed);
|
|
119
|
+
return Number.isFinite(ms) ? ms : void 0;
|
|
120
|
+
}
|
|
121
|
+
function createCacheEntry(value, headers, statusCode) {
|
|
122
|
+
var _a, _b, _c, _d;
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const dateMs = (_a = parseHttpDate(headers.get("date"))) != null ? _a : now;
|
|
125
|
+
const ageRaw = headers.get("age");
|
|
126
|
+
const ageHeader = ageRaw !== null ? Number.parseInt(ageRaw.trim(), 10) || 0 : 0;
|
|
127
|
+
return {
|
|
128
|
+
__cacheEntry: true,
|
|
129
|
+
value,
|
|
130
|
+
metadata: {
|
|
131
|
+
etag: (_b = headers.get("etag")) != null ? _b : void 0,
|
|
132
|
+
lastModified: (_c = headers.get("last-modified")) != null ? _c : void 0,
|
|
133
|
+
cacheControl: parseCacheControl(headers.get("cache-control")),
|
|
134
|
+
responseDate: dateMs,
|
|
135
|
+
storedAt: now,
|
|
136
|
+
ageHeader,
|
|
137
|
+
varyHeaders: (_d = headers.get("vary")) != null ? _d : void 0,
|
|
138
|
+
statusCode,
|
|
139
|
+
expires: parseHttpDate(headers.get("expires"))
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function refreshCacheEntry(existing, newHeaders) {
|
|
144
|
+
var _a;
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
const dateMs = (_a = parseHttpDate(newHeaders.get("date"))) != null ? _a : now;
|
|
147
|
+
const ageRaw = newHeaders.get("age");
|
|
148
|
+
const ageHeader = ageRaw !== null ? Number.parseInt(ageRaw.trim(), 10) || 0 : 0;
|
|
149
|
+
const newCacheControl = newHeaders.get("cache-control");
|
|
150
|
+
const newEtag = newHeaders.get("etag");
|
|
151
|
+
const newLastModified = newHeaders.get("last-modified");
|
|
152
|
+
const newExpires = newHeaders.get("expires");
|
|
153
|
+
const newVary = newHeaders.get("vary");
|
|
154
|
+
return {
|
|
155
|
+
__cacheEntry: true,
|
|
156
|
+
value: existing.value,
|
|
157
|
+
metadata: __spreadProps(__spreadValues({}, existing.metadata), {
|
|
158
|
+
cacheControl: newCacheControl ? parseCacheControl(newCacheControl) : existing.metadata.cacheControl,
|
|
159
|
+
etag: newEtag != null ? newEtag : existing.metadata.etag,
|
|
160
|
+
lastModified: newLastModified != null ? newLastModified : existing.metadata.lastModified,
|
|
161
|
+
responseDate: dateMs,
|
|
162
|
+
storedAt: now,
|
|
163
|
+
ageHeader,
|
|
164
|
+
expires: newExpires !== null ? parseHttpDate(newExpires) : existing.metadata.expires,
|
|
165
|
+
varyHeaders: newVary != null ? newVary : existing.metadata.varyHeaders
|
|
166
|
+
// statusCode stays the same (the original 200, not 304)
|
|
167
|
+
})
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/cache/freshness.ts
|
|
172
|
+
function calculateFreshnessLifetime(metadata) {
|
|
173
|
+
const { cacheControl } = metadata;
|
|
174
|
+
if (cacheControl.maxAge !== void 0) {
|
|
175
|
+
return cacheControl.maxAge;
|
|
176
|
+
}
|
|
177
|
+
if (metadata.expires !== void 0) {
|
|
178
|
+
if (metadata.expires === 0) return 0;
|
|
179
|
+
const delta = (metadata.expires - metadata.responseDate) / 1e3;
|
|
180
|
+
return Math.max(0, delta);
|
|
181
|
+
}
|
|
182
|
+
if (metadata.lastModified) {
|
|
183
|
+
const lastModMs = Date.parse(metadata.lastModified);
|
|
184
|
+
if (Number.isFinite(lastModMs)) {
|
|
185
|
+
const age = (metadata.responseDate - lastModMs) / 1e3;
|
|
186
|
+
if (age > 0) {
|
|
187
|
+
return Math.floor(age * 0.1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
function calculateCurrentAge(metadata, now) {
|
|
194
|
+
const currentTime = now != null ? now : Date.now();
|
|
195
|
+
const responseTime = metadata.storedAt;
|
|
196
|
+
const apparentAge = Math.max(
|
|
197
|
+
0,
|
|
198
|
+
(responseTime - metadata.responseDate) / 1e3
|
|
199
|
+
);
|
|
200
|
+
const correctedAgeValue = metadata.ageHeader;
|
|
201
|
+
const correctedInitialAge = Math.max(apparentAge, correctedAgeValue);
|
|
202
|
+
const residentTime = (currentTime - responseTime) / 1e3;
|
|
203
|
+
return correctedInitialAge + residentTime;
|
|
204
|
+
}
|
|
205
|
+
function getFreshnessStatus(metadata, now) {
|
|
206
|
+
const { cacheControl } = metadata;
|
|
207
|
+
if (cacheControl.noCache) {
|
|
208
|
+
return "no-cache";
|
|
209
|
+
}
|
|
210
|
+
const freshnessLifetime = calculateFreshnessLifetime(metadata);
|
|
211
|
+
const currentAge = calculateCurrentAge(metadata, now);
|
|
212
|
+
if (freshnessLifetime > currentAge) {
|
|
213
|
+
return "fresh";
|
|
214
|
+
}
|
|
215
|
+
const staleness = currentAge - freshnessLifetime;
|
|
216
|
+
if (cacheControl.mustRevalidate) {
|
|
217
|
+
return "must-revalidate";
|
|
218
|
+
}
|
|
219
|
+
if (cacheControl.staleWhileRevalidate !== void 0 && staleness <= cacheControl.staleWhileRevalidate) {
|
|
220
|
+
return "stale-while-revalidate";
|
|
221
|
+
}
|
|
222
|
+
if (cacheControl.staleIfError !== void 0 && staleness <= cacheControl.staleIfError) {
|
|
223
|
+
return "stale-if-error";
|
|
224
|
+
}
|
|
225
|
+
return "stale";
|
|
226
|
+
}
|
|
227
|
+
function calculateStoreTTL(metadata, defaultTTL) {
|
|
228
|
+
var _a, _b;
|
|
229
|
+
const freshness = calculateFreshnessLifetime(metadata);
|
|
230
|
+
if (freshness === 0 && metadata.cacheControl.maxAge === void 0) {
|
|
231
|
+
return defaultTTL;
|
|
232
|
+
}
|
|
233
|
+
const swrWindow = (_a = metadata.cacheControl.staleWhileRevalidate) != null ? _a : 0;
|
|
234
|
+
const sieWindow = (_b = metadata.cacheControl.staleIfError) != null ? _b : 0;
|
|
235
|
+
const staleWindow = Math.max(swrWindow, sieWindow);
|
|
236
|
+
return freshness + staleWindow;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/cache/vary.ts
|
|
240
|
+
function parseVaryHeader(varyHeader) {
|
|
241
|
+
if (!varyHeader) return [];
|
|
242
|
+
const trimmed = varyHeader.trim();
|
|
243
|
+
if (trimmed === "*") return ["*"];
|
|
244
|
+
return trimmed.split(",").map((f) => f.trim().toLowerCase()).filter(Boolean);
|
|
245
|
+
}
|
|
246
|
+
function captureVaryValues(varyFields, requestHeaders) {
|
|
247
|
+
var _a;
|
|
248
|
+
const values = {};
|
|
249
|
+
for (const field of varyFields) {
|
|
250
|
+
const lower = field.toLowerCase();
|
|
251
|
+
values[lower] = (_a = requestHeaders[lower]) != null ? _a : requestHeaders[field];
|
|
252
|
+
}
|
|
253
|
+
return values;
|
|
254
|
+
}
|
|
255
|
+
function varyMatches(cachedVaryValues, cachedVaryHeader, currentRequestHeaders) {
|
|
256
|
+
var _a;
|
|
257
|
+
if (!cachedVaryHeader) return true;
|
|
258
|
+
const fields = parseVaryHeader(cachedVaryHeader);
|
|
259
|
+
if (fields.length === 0) return true;
|
|
260
|
+
if (fields[0] === "*") return false;
|
|
261
|
+
if (!cachedVaryValues) return false;
|
|
262
|
+
for (const field of fields) {
|
|
263
|
+
const lower = field.toLowerCase();
|
|
264
|
+
const cachedVal = cachedVaryValues[lower];
|
|
265
|
+
const currentVal = (_a = currentRequestHeaders[lower]) != null ? _a : currentRequestHeaders[field];
|
|
266
|
+
if (cachedVal !== currentVal) return false;
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
32
271
|
// src/errors/http-client-error.ts
|
|
33
272
|
var HttpClientError = class extends Error {
|
|
34
273
|
constructor(message, statusCode) {
|
|
@@ -263,16 +502,18 @@ function wait(ms, signal) {
|
|
|
263
502
|
var HttpClient = class {
|
|
264
503
|
constructor(stores = {}, options = {}) {
|
|
265
504
|
this.serverCooldowns = /* @__PURE__ */ new Map();
|
|
266
|
-
|
|
267
|
-
|
|
505
|
+
this.pendingRevalidations = [];
|
|
506
|
+
var _a, _b, _c, _d;
|
|
268
507
|
this.stores = stores;
|
|
269
508
|
this.options = {
|
|
270
509
|
defaultCacheTTL: (_a = options.defaultCacheTTL) != null ? _a : 3600,
|
|
271
510
|
throwOnRateLimit: (_b = options.throwOnRateLimit) != null ? _b : true,
|
|
272
511
|
maxWaitTime: (_c = options.maxWaitTime) != null ? _c : 6e4,
|
|
512
|
+
respectCacheHeaders: (_d = options.respectCacheHeaders) != null ? _d : false,
|
|
273
513
|
responseTransformer: options.responseTransformer,
|
|
274
514
|
errorHandler: options.errorHandler,
|
|
275
515
|
responseHandler: options.responseHandler,
|
|
516
|
+
cacheHeaderOverrides: options.cacheHeaderOverrides,
|
|
276
517
|
rateLimitHeaders: this.normalizeRateLimitHeaders(
|
|
277
518
|
options.rateLimitHeaders
|
|
278
519
|
)
|
|
@@ -361,6 +602,15 @@ var HttpClient = class {
|
|
|
361
602
|
if (!headers) {
|
|
362
603
|
return void 0;
|
|
363
604
|
}
|
|
605
|
+
if (headers instanceof Headers) {
|
|
606
|
+
for (const rawName of names) {
|
|
607
|
+
const value = headers.get(rawName);
|
|
608
|
+
if (value !== null) {
|
|
609
|
+
return value;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
364
614
|
for (const rawName of names) {
|
|
365
615
|
const name = rawName.toLowerCase();
|
|
366
616
|
const value = (_a = headers[name]) != null ? _a : headers[rawName];
|
|
@@ -487,17 +737,24 @@ var HttpClient = class {
|
|
|
487
737
|
return __async(this, null, function* () {
|
|
488
738
|
const rateLimit = this.stores.rateLimit;
|
|
489
739
|
const startedAt = Date.now();
|
|
740
|
+
const hasAtomicAcquire = typeof rateLimit.acquire === "function";
|
|
741
|
+
const canProceedNow = () => __async(this, null, function* () {
|
|
742
|
+
if (hasAtomicAcquire) {
|
|
743
|
+
return rateLimit.acquire(resource, priority);
|
|
744
|
+
}
|
|
745
|
+
return rateLimit.canProceed(resource, priority);
|
|
746
|
+
});
|
|
490
747
|
if (this.options.throwOnRateLimit) {
|
|
491
|
-
const canProceed = yield
|
|
748
|
+
const canProceed = yield canProceedNow();
|
|
492
749
|
if (!canProceed) {
|
|
493
750
|
const waitTime = yield rateLimit.getWaitTime(resource, priority);
|
|
494
751
|
throw new Error(
|
|
495
752
|
`Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`
|
|
496
753
|
);
|
|
497
754
|
}
|
|
498
|
-
return;
|
|
755
|
+
return hasAtomicAcquire;
|
|
499
756
|
}
|
|
500
|
-
while (!(yield
|
|
757
|
+
while (!(yield canProceedNow())) {
|
|
501
758
|
const suggestedWaitMs = yield rateLimit.getWaitTime(resource, priority);
|
|
502
759
|
const elapsedMs = Date.now() - startedAt;
|
|
503
760
|
const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;
|
|
@@ -509,34 +766,181 @@ var HttpClient = class {
|
|
|
509
766
|
const waitTime = suggestedWaitMs > 0 ? Math.min(suggestedWaitMs, remainingWaitBudgetMs) : Math.min(25, remainingWaitBudgetMs);
|
|
510
767
|
yield wait(waitTime, signal);
|
|
511
768
|
}
|
|
769
|
+
return hasAtomicAcquire;
|
|
512
770
|
});
|
|
513
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Wait for all pending background revalidations to complete.
|
|
774
|
+
* Primarily useful in tests to avoid dangling promises.
|
|
775
|
+
*/
|
|
776
|
+
flushRevalidations() {
|
|
777
|
+
return __async(this, null, function* () {
|
|
778
|
+
yield Promise.allSettled(this.pendingRevalidations);
|
|
779
|
+
this.pendingRevalidations = [];
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
backgroundRevalidate(url, hash, entry) {
|
|
783
|
+
return __async(this, null, function* () {
|
|
784
|
+
var _a, _b;
|
|
785
|
+
const headers = new Headers();
|
|
786
|
+
if (entry.metadata.etag) {
|
|
787
|
+
headers.set("If-None-Match", entry.metadata.etag);
|
|
788
|
+
}
|
|
789
|
+
if (entry.metadata.lastModified) {
|
|
790
|
+
headers.set("If-Modified-Since", entry.metadata.lastModified);
|
|
791
|
+
}
|
|
792
|
+
try {
|
|
793
|
+
const response = yield fetch(url, { headers });
|
|
794
|
+
this.applyServerRateLimitHints(url, response.headers, response.status);
|
|
795
|
+
if (response.status === 304) {
|
|
796
|
+
const refreshed = refreshCacheEntry(entry, response.headers);
|
|
797
|
+
const ttl = this.clampTTL(
|
|
798
|
+
calculateStoreTTL(refreshed.metadata, this.options.defaultCacheTTL)
|
|
799
|
+
);
|
|
800
|
+
yield (_a = this.stores.cache) == null ? void 0 : _a.set(hash, refreshed, ttl);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (response.ok) {
|
|
804
|
+
const parsedBody = yield this.parseResponseBody(response);
|
|
805
|
+
let data = parsedBody.data;
|
|
806
|
+
if (this.options.responseTransformer && data) {
|
|
807
|
+
data = this.options.responseTransformer(data);
|
|
808
|
+
}
|
|
809
|
+
if (this.options.responseHandler) {
|
|
810
|
+
data = this.options.responseHandler(data);
|
|
811
|
+
}
|
|
812
|
+
const newEntry = createCacheEntry(
|
|
813
|
+
data,
|
|
814
|
+
response.headers,
|
|
815
|
+
response.status
|
|
816
|
+
);
|
|
817
|
+
const ttl = this.clampTTL(
|
|
818
|
+
calculateStoreTTL(newEntry.metadata, this.options.defaultCacheTTL)
|
|
819
|
+
);
|
|
820
|
+
yield (_b = this.stores.cache) == null ? void 0 : _b.set(hash, newEntry, ttl);
|
|
821
|
+
}
|
|
822
|
+
} catch (e) {
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
clampTTL(ttl) {
|
|
827
|
+
const overrides = this.options.cacheHeaderOverrides;
|
|
828
|
+
if (!overrides) return ttl;
|
|
829
|
+
let clamped = ttl;
|
|
830
|
+
if (overrides.minimumTTL !== void 0) {
|
|
831
|
+
clamped = Math.max(clamped, overrides.minimumTTL);
|
|
832
|
+
}
|
|
833
|
+
if (overrides.maximumTTL !== void 0) {
|
|
834
|
+
clamped = Math.min(clamped, overrides.maximumTTL);
|
|
835
|
+
}
|
|
836
|
+
return clamped;
|
|
837
|
+
}
|
|
838
|
+
isServerErrorOrNetworkFailure(error) {
|
|
839
|
+
var _a;
|
|
840
|
+
if (typeof error === "object" && error !== null && "response" in error) {
|
|
841
|
+
const status = (_a = error.response) == null ? void 0 : _a.status;
|
|
842
|
+
if (typeof status === "number" && status >= 500) return true;
|
|
843
|
+
}
|
|
844
|
+
if (error instanceof TypeError) return true;
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
514
847
|
generateClientError(err) {
|
|
515
|
-
var _a, _b
|
|
848
|
+
var _a, _b;
|
|
516
849
|
if (this.options.errorHandler) {
|
|
517
850
|
return this.options.errorHandler(err);
|
|
518
851
|
}
|
|
519
852
|
if (err instanceof HttpClientError) {
|
|
520
853
|
return err;
|
|
521
854
|
}
|
|
522
|
-
const
|
|
523
|
-
const statusCode = (_a =
|
|
524
|
-
const
|
|
525
|
-
const
|
|
855
|
+
const responseError = err;
|
|
856
|
+
const statusCode = typeof ((_a = responseError.response) == null ? void 0 : _a.status) === "number" ? responseError.response.status : void 0;
|
|
857
|
+
const responseData = (_b = responseError.response) == null ? void 0 : _b.data;
|
|
858
|
+
const derivedResponseMessage = typeof responseData === "object" && responseData !== null ? responseData.message : void 0;
|
|
859
|
+
const responseMessage = typeof derivedResponseMessage === "string" ? derivedResponseMessage : void 0;
|
|
860
|
+
const errorMessage = err instanceof Error ? err.message : typeof err.message === "string" ? err.message : "Unknown error";
|
|
861
|
+
const message = `${errorMessage}${responseMessage ? `, ${responseMessage}` : ""}`;
|
|
526
862
|
return new HttpClientError(message, statusCode);
|
|
527
863
|
}
|
|
864
|
+
parseResponseBody(response) {
|
|
865
|
+
return __async(this, null, function* () {
|
|
866
|
+
var _a, _b;
|
|
867
|
+
if (response.status === 204 || response.status === 205) {
|
|
868
|
+
return { data: void 0 };
|
|
869
|
+
}
|
|
870
|
+
const rawBody = yield response.text();
|
|
871
|
+
if (!rawBody) {
|
|
872
|
+
return { data: void 0 };
|
|
873
|
+
}
|
|
874
|
+
const contentType = (_b = (_a = response.headers.get("content-type")) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
|
|
875
|
+
const shouldAttemptJsonParsing = contentType.includes("application/json") || contentType.includes("+json") || rawBody.trimStart().startsWith("{") || rawBody.trimStart().startsWith("[");
|
|
876
|
+
if (!shouldAttemptJsonParsing) {
|
|
877
|
+
return { data: rawBody };
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
const parsed = JSON.parse(rawBody);
|
|
881
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
882
|
+
return { data: parsed };
|
|
883
|
+
}
|
|
884
|
+
return { data: parsed };
|
|
885
|
+
} catch (e) {
|
|
886
|
+
return { data: rawBody };
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
528
890
|
get(_0) {
|
|
529
891
|
return __async(this, arguments, function* (url, options = {}) {
|
|
892
|
+
var _a, _b;
|
|
530
893
|
const { signal, priority = "background" } = options;
|
|
531
894
|
const { endpoint, params } = this.parseUrlForHashing(url);
|
|
532
895
|
const hash = hashRequest(endpoint, params);
|
|
533
896
|
const resource = this.inferResource(url);
|
|
897
|
+
let staleEntry;
|
|
898
|
+
let staleCandidate;
|
|
534
899
|
try {
|
|
535
900
|
yield this.enforceServerCooldown(url, signal);
|
|
536
901
|
if (this.stores.cache) {
|
|
537
902
|
const cachedResult = yield this.stores.cache.get(hash);
|
|
538
903
|
if (cachedResult !== void 0) {
|
|
539
|
-
|
|
904
|
+
if (this.options.respectCacheHeaders && isCacheEntry(cachedResult)) {
|
|
905
|
+
const entry = cachedResult;
|
|
906
|
+
const status = getFreshnessStatus(entry.metadata);
|
|
907
|
+
switch (status) {
|
|
908
|
+
case "fresh":
|
|
909
|
+
return entry.value;
|
|
910
|
+
case "no-cache":
|
|
911
|
+
if ((_a = this.options.cacheHeaderOverrides) == null ? void 0 : _a.ignoreNoCache) {
|
|
912
|
+
return entry.value;
|
|
913
|
+
}
|
|
914
|
+
staleEntry = entry;
|
|
915
|
+
break;
|
|
916
|
+
case "must-revalidate":
|
|
917
|
+
staleEntry = entry;
|
|
918
|
+
break;
|
|
919
|
+
case "stale-while-revalidate": {
|
|
920
|
+
const revalidation = this.backgroundRevalidate(
|
|
921
|
+
url,
|
|
922
|
+
hash,
|
|
923
|
+
entry
|
|
924
|
+
);
|
|
925
|
+
this.pendingRevalidations.push(revalidation);
|
|
926
|
+
revalidation.finally(() => {
|
|
927
|
+
this.pendingRevalidations = this.pendingRevalidations.filter(
|
|
928
|
+
(p) => p !== revalidation
|
|
929
|
+
);
|
|
930
|
+
});
|
|
931
|
+
return entry.value;
|
|
932
|
+
}
|
|
933
|
+
case "stale-if-error":
|
|
934
|
+
staleCandidate = entry;
|
|
935
|
+
staleEntry = entry;
|
|
936
|
+
break;
|
|
937
|
+
case "stale":
|
|
938
|
+
staleEntry = entry;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
} else if (!this.options.respectCacheHeaders) {
|
|
942
|
+
return cachedResult;
|
|
943
|
+
}
|
|
540
944
|
}
|
|
541
945
|
}
|
|
542
946
|
if (this.stores.dedupe) {
|
|
@@ -556,16 +960,59 @@ var HttpClient = class {
|
|
|
556
960
|
yield this.stores.dedupe.register(hash);
|
|
557
961
|
}
|
|
558
962
|
}
|
|
963
|
+
let alreadyRecordedRateLimit = false;
|
|
559
964
|
if (this.stores.rateLimit) {
|
|
560
|
-
yield this.enforceStoreRateLimit(
|
|
965
|
+
alreadyRecordedRateLimit = yield this.enforceStoreRateLimit(
|
|
966
|
+
resource,
|
|
967
|
+
priority,
|
|
968
|
+
signal
|
|
969
|
+
);
|
|
561
970
|
}
|
|
562
|
-
const
|
|
563
|
-
this.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
971
|
+
const fetchInit = { signal };
|
|
972
|
+
if (this.options.respectCacheHeaders && staleEntry) {
|
|
973
|
+
const conditionalHeaders = new Headers();
|
|
974
|
+
if (staleEntry.metadata.etag) {
|
|
975
|
+
conditionalHeaders.set("If-None-Match", staleEntry.metadata.etag);
|
|
976
|
+
}
|
|
977
|
+
if (staleEntry.metadata.lastModified) {
|
|
978
|
+
conditionalHeaders.set(
|
|
979
|
+
"If-Modified-Since",
|
|
980
|
+
staleEntry.metadata.lastModified
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
if ([...conditionalHeaders].length > 0) {
|
|
984
|
+
fetchInit.headers = conditionalHeaders;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const response = yield fetch(url, fetchInit);
|
|
988
|
+
this.applyServerRateLimitHints(url, response.headers, response.status);
|
|
989
|
+
if (this.options.respectCacheHeaders && response.status === 304 && staleEntry) {
|
|
990
|
+
const refreshed = refreshCacheEntry(staleEntry, response.headers);
|
|
991
|
+
const ttl = this.clampTTL(
|
|
992
|
+
calculateStoreTTL(refreshed.metadata, this.options.defaultCacheTTL)
|
|
993
|
+
);
|
|
994
|
+
if (this.stores.cache) {
|
|
995
|
+
yield this.stores.cache.set(hash, refreshed, ttl);
|
|
996
|
+
}
|
|
997
|
+
const result2 = refreshed.value;
|
|
998
|
+
if (this.stores.dedupe) {
|
|
999
|
+
yield this.stores.dedupe.complete(hash, result2);
|
|
1000
|
+
}
|
|
1001
|
+
return result2;
|
|
1002
|
+
}
|
|
1003
|
+
const parsedBody = yield this.parseResponseBody(response);
|
|
1004
|
+
if (!response.ok) {
|
|
1005
|
+
const error = {
|
|
1006
|
+
message: `Request failed with status ${response.status}`,
|
|
1007
|
+
response: {
|
|
1008
|
+
status: response.status,
|
|
1009
|
+
data: parsedBody.data,
|
|
1010
|
+
headers: response.headers
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
let data = parsedBody.data;
|
|
569
1016
|
if (this.options.responseTransformer && data) {
|
|
570
1017
|
data = this.options.responseTransformer(data);
|
|
571
1018
|
}
|
|
@@ -573,25 +1020,44 @@ var HttpClient = class {
|
|
|
573
1020
|
data = this.options.responseHandler(data);
|
|
574
1021
|
}
|
|
575
1022
|
const result = data;
|
|
576
|
-
if (this.stores.rateLimit) {
|
|
1023
|
+
if (this.stores.rateLimit && !alreadyRecordedRateLimit) {
|
|
577
1024
|
const rateLimit = this.stores.rateLimit;
|
|
578
1025
|
yield rateLimit.record(resource, priority);
|
|
579
1026
|
}
|
|
580
1027
|
if (this.stores.cache) {
|
|
581
|
-
|
|
1028
|
+
if (this.options.respectCacheHeaders) {
|
|
1029
|
+
const cc = parseCacheControl(response.headers.get("cache-control"));
|
|
1030
|
+
const shouldStore = !cc.noStore || ((_b = this.options.cacheHeaderOverrides) == null ? void 0 : _b.ignoreNoStore);
|
|
1031
|
+
if (shouldStore) {
|
|
1032
|
+
const entry = createCacheEntry(
|
|
1033
|
+
result,
|
|
1034
|
+
response.headers,
|
|
1035
|
+
response.status
|
|
1036
|
+
);
|
|
1037
|
+
const ttl = this.clampTTL(
|
|
1038
|
+
calculateStoreTTL(entry.metadata, this.options.defaultCacheTTL)
|
|
1039
|
+
);
|
|
1040
|
+
yield this.stores.cache.set(hash, entry, ttl);
|
|
1041
|
+
}
|
|
1042
|
+
} else {
|
|
1043
|
+
yield this.stores.cache.set(
|
|
1044
|
+
hash,
|
|
1045
|
+
result,
|
|
1046
|
+
this.options.defaultCacheTTL
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
582
1049
|
}
|
|
583
1050
|
if (this.stores.dedupe) {
|
|
584
1051
|
yield this.stores.dedupe.complete(hash, result);
|
|
585
1052
|
}
|
|
586
1053
|
return result;
|
|
587
1054
|
} catch (error) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
this.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
);
|
|
1055
|
+
if (this.options.respectCacheHeaders && staleCandidate && this.isServerErrorOrNetworkFailure(error)) {
|
|
1056
|
+
const result = staleCandidate.value;
|
|
1057
|
+
if (this.stores.dedupe) {
|
|
1058
|
+
yield this.stores.dedupe.complete(hash, result);
|
|
1059
|
+
}
|
|
1060
|
+
return result;
|
|
595
1061
|
}
|
|
596
1062
|
if (this.stores.dedupe) {
|
|
597
1063
|
yield this.stores.dedupe.fail(hash, error);
|
|
@@ -599,6 +1065,9 @@ var HttpClient = class {
|
|
|
599
1065
|
if (error instanceof Error && error.name === "AbortError") {
|
|
600
1066
|
throw error;
|
|
601
1067
|
}
|
|
1068
|
+
if (error instanceof HttpClientError) {
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
602
1071
|
throw this.generateClientError(error);
|
|
603
1072
|
}
|
|
604
1073
|
});
|
|
@@ -610,6 +1079,18 @@ exports.AdaptiveConfigSchema = AdaptiveConfigSchema;
|
|
|
610
1079
|
exports.DEFAULT_RATE_LIMIT = DEFAULT_RATE_LIMIT;
|
|
611
1080
|
exports.HttpClient = HttpClient;
|
|
612
1081
|
exports.HttpClientError = HttpClientError;
|
|
1082
|
+
exports.calculateCurrentAge = calculateCurrentAge;
|
|
1083
|
+
exports.calculateFreshnessLifetime = calculateFreshnessLifetime;
|
|
1084
|
+
exports.calculateStoreTTL = calculateStoreTTL;
|
|
1085
|
+
exports.captureVaryValues = captureVaryValues;
|
|
1086
|
+
exports.createCacheEntry = createCacheEntry;
|
|
1087
|
+
exports.getFreshnessStatus = getFreshnessStatus;
|
|
613
1088
|
exports.hashRequest = hashRequest;
|
|
1089
|
+
exports.isCacheEntry = isCacheEntry;
|
|
1090
|
+
exports.parseCacheControl = parseCacheControl;
|
|
1091
|
+
exports.parseHttpDate = parseHttpDate;
|
|
1092
|
+
exports.parseVaryHeader = parseVaryHeader;
|
|
1093
|
+
exports.refreshCacheEntry = refreshCacheEntry;
|
|
1094
|
+
exports.varyMatches = varyMatches;
|
|
614
1095
|
//# sourceMappingURL=index.cjs.map
|
|
615
1096
|
//# sourceMappingURL=index.cjs.map
|