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