@foundatiofx/fetchclient 0.47.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/esm/mod.js +5 -0
- package/esm/package.json +3 -0
- package/esm/src/Counter.js +36 -0
- package/esm/src/DefaultHelpers.js +132 -0
- package/esm/src/FetchClient.js +543 -0
- package/esm/src/FetchClientCache.js +88 -0
- package/esm/src/FetchClientContext.js +1 -0
- package/esm/src/FetchClientMiddleware.js +1 -0
- package/esm/src/FetchClientOptions.js +1 -0
- package/esm/src/FetchClientProvider.js +200 -0
- package/esm/src/FetchClientResponse.js +1 -0
- package/esm/src/LinkHeader.js +70 -0
- package/esm/src/ObjectEvent.js +15 -0
- package/esm/src/ProblemDetails.js +47 -0
- package/esm/src/RateLimitMiddleware.js +115 -0
- package/esm/src/RateLimiter.js +347 -0
- package/esm/src/RequestOptions.js +1 -0
- package/license +20 -0
- package/package.json +50 -0
- package/readme.md +303 -0
- package/script/mod.js +27 -0
- package/script/package.json +3 -0
- package/script/src/Counter.js +40 -0
- package/script/src/DefaultHelpers.js +149 -0
- package/script/src/FetchClient.js +547 -0
- package/script/src/FetchClientCache.js +92 -0
- package/script/src/FetchClientContext.js +2 -0
- package/script/src/FetchClientMiddleware.js +2 -0
- package/script/src/FetchClientOptions.js +2 -0
- package/script/src/FetchClientProvider.js +204 -0
- package/script/src/FetchClientResponse.js +2 -0
- package/script/src/LinkHeader.js +72 -0
- package/script/src/ObjectEvent.js +19 -0
- package/script/src/ProblemDetails.js +51 -0
- package/script/src/RateLimitMiddleware.js +120 -0
- package/script/src/RateLimiter.js +356 -0
- package/script/src/RequestOptions.js +2 -0
- package/types/_dnt.test_shims.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/almost_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/array_includes.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/assert.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/assertion_error.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/exists.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/fail.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/false.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/greater.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/greater_or_equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/instance_of.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/is_error.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/less.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/less_or_equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/match.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/mod.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/not_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/not_instance_of.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/not_match.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/not_strict_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/object_match.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/rejects.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/strict_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/string_includes.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/throws.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/unimplemented.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/unreachable.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/1.0.10/build_message.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/1.0.10/diff.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/1.0.10/diff_str.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/1.0.10/format.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/1.0.10/styles.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/1.0.10/types.d.ts.map +1 -0
- package/types/mod.d.ts +11 -0
- package/types/mod.d.ts.map +1 -0
- package/types/src/Counter.d.ts +27 -0
- package/types/src/Counter.d.ts.map +1 -0
- package/types/src/DefaultHelpers.d.ts +106 -0
- package/types/src/DefaultHelpers.d.ts.map +1 -0
- package/types/src/FetchClient.d.ts +156 -0
- package/types/src/FetchClient.d.ts.map +1 -0
- package/types/src/FetchClient.test.d.ts.map +1 -0
- package/types/src/FetchClientCache.d.ts +62 -0
- package/types/src/FetchClientCache.d.ts.map +1 -0
- package/types/src/FetchClientContext.d.ts +8 -0
- package/types/src/FetchClientContext.d.ts.map +1 -0
- package/types/src/FetchClientMiddleware.d.ts +9 -0
- package/types/src/FetchClientMiddleware.d.ts.map +1 -0
- package/types/src/FetchClientOptions.d.ts +53 -0
- package/types/src/FetchClientOptions.d.ts.map +1 -0
- package/types/src/FetchClientProvider.d.ts +109 -0
- package/types/src/FetchClientProvider.d.ts.map +1 -0
- package/types/src/FetchClientResponse.d.ts +29 -0
- package/types/src/FetchClientResponse.d.ts.map +1 -0
- package/types/src/LinkHeader.d.ts +15 -0
- package/types/src/LinkHeader.d.ts.map +1 -0
- package/types/src/ObjectEvent.d.ts +20 -0
- package/types/src/ObjectEvent.d.ts.map +1 -0
- package/types/src/ProblemDetails.d.ts +43 -0
- package/types/src/ProblemDetails.d.ts.map +1 -0
- package/types/src/RateLimit.test.d.ts.map +1 -0
- package/types/src/RateLimitMiddleware.d.ts +50 -0
- package/types/src/RateLimitMiddleware.d.ts.map +1 -0
- package/types/src/RateLimiter.d.ts +179 -0
- package/types/src/RateLimiter.d.ts.map +1 -0
- package/types/src/RequestOptions.d.ts +64 -0
- package/types/src/RequestOptions.d.ts.map +1 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A rate limiter that tracks requests per time window.
|
|
3
|
+
*/
|
|
4
|
+
export class RateLimiter {
|
|
5
|
+
options;
|
|
6
|
+
buckets = new Map();
|
|
7
|
+
groupOptions = new Map();
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = {
|
|
10
|
+
getGroupFunc: () => "global",
|
|
11
|
+
onRateLimitExceeded: () => { },
|
|
12
|
+
groups: {},
|
|
13
|
+
...options,
|
|
14
|
+
};
|
|
15
|
+
// Initialize group options if provided
|
|
16
|
+
if (options.groups) {
|
|
17
|
+
for (const [groupKey, groupOptions] of Object.entries(options.groups)) {
|
|
18
|
+
this.groupOptions.set(groupKey, groupOptions);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a request is allowed and updates the rate limit state.
|
|
24
|
+
* @param url - The request URL
|
|
25
|
+
* @returns True if the request is allowed, false if rate limit is exceeded
|
|
26
|
+
*/
|
|
27
|
+
isAllowed(url) {
|
|
28
|
+
const key = this.options.getGroupFunc(url);
|
|
29
|
+
const groupOptions = this.getGroupOptions(key);
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
// Use group-specific options if available, otherwise fall back to global options
|
|
32
|
+
const maxRequests = groupOptions.maxRequests ?? 0;
|
|
33
|
+
const windowSeconds = groupOptions.windowSeconds ?? 0;
|
|
34
|
+
const onRateLimitExceeded = groupOptions.onRateLimitExceeded ??
|
|
35
|
+
this.options.onRateLimitExceeded;
|
|
36
|
+
let bucket = this.buckets.get(key);
|
|
37
|
+
if (!bucket) {
|
|
38
|
+
bucket = {
|
|
39
|
+
requests: [],
|
|
40
|
+
resetTime: now + (windowSeconds * 1000),
|
|
41
|
+
};
|
|
42
|
+
this.buckets.set(key, bucket);
|
|
43
|
+
}
|
|
44
|
+
// Clean up old requests outside the time window
|
|
45
|
+
const windowStart = now - (windowSeconds * 1000);
|
|
46
|
+
bucket.requests = bucket.requests.filter((time) => time > windowStart);
|
|
47
|
+
// Update reset time if all requests have expired
|
|
48
|
+
if (bucket.requests.length === 0) {
|
|
49
|
+
bucket.resetTime = now + (windowSeconds * 1000);
|
|
50
|
+
}
|
|
51
|
+
// Check if we're within the rate limit
|
|
52
|
+
if (bucket.requests.length >= maxRequests) {
|
|
53
|
+
onRateLimitExceeded(bucket.resetTime);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Add the current request
|
|
57
|
+
bucket.requests.push(now);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Gets the current request count for a specific key.
|
|
62
|
+
* @param url - The request URL
|
|
63
|
+
* @returns The current number of requests in the time window
|
|
64
|
+
*/
|
|
65
|
+
getRequestCount(url) {
|
|
66
|
+
const key = this.options.getGroupFunc(url);
|
|
67
|
+
const groupOptions = this.getGroupOptions(key);
|
|
68
|
+
const bucket = this.buckets.get(key);
|
|
69
|
+
if (!bucket) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const windowSeconds = groupOptions.windowSeconds ?? 0;
|
|
74
|
+
const windowStart = now - (windowSeconds * 1000);
|
|
75
|
+
return bucket.requests.filter((time) => time > windowStart).length;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Gets the remaining requests allowed for a specific key.
|
|
79
|
+
* @param url - The request URL
|
|
80
|
+
* @returns The number of remaining requests allowed
|
|
81
|
+
*/
|
|
82
|
+
getRemainingRequests(url) {
|
|
83
|
+
const key = this.options.getGroupFunc(url);
|
|
84
|
+
const groupOptions = this.getGroupOptions(key);
|
|
85
|
+
const maxRequests = groupOptions.maxRequests ?? 0;
|
|
86
|
+
return Math.max(0, maxRequests - this.getRequestCount(url));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Gets the time when the rate limit will reset for a specific key.
|
|
90
|
+
* @param url - The request URL
|
|
91
|
+
* @returns The reset time in milliseconds since epoch, or null if no bucket exists
|
|
92
|
+
*/
|
|
93
|
+
getResetTime(url) {
|
|
94
|
+
const key = this.options.getGroupFunc(url);
|
|
95
|
+
const bucket = this.buckets.get(key);
|
|
96
|
+
return bucket?.resetTime ?? null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clears the rate limit state for a specific key.
|
|
100
|
+
* @param url - The request URL
|
|
101
|
+
*/
|
|
102
|
+
clearBucket(url) {
|
|
103
|
+
const key = this.options.getGroupFunc(url);
|
|
104
|
+
this.buckets.delete(key);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Gets the group key for a URL.
|
|
108
|
+
* @param url - The request URL
|
|
109
|
+
* @returns The group key
|
|
110
|
+
*/
|
|
111
|
+
getGroup(url) {
|
|
112
|
+
return this.options.getGroupFunc(url);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Gets the options for a specific group. Falls back to global options if not set.
|
|
116
|
+
* @param group - The group key
|
|
117
|
+
* @returns The options for the group
|
|
118
|
+
*/
|
|
119
|
+
getGroupOptions(group) {
|
|
120
|
+
const options = this.groupOptions.get(group);
|
|
121
|
+
if (!options) {
|
|
122
|
+
return {
|
|
123
|
+
maxRequests: this.options.maxRequests,
|
|
124
|
+
windowSeconds: this.options.windowSeconds,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return options;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Checks if a group has specific options set.
|
|
131
|
+
* @param group - The group key
|
|
132
|
+
* @returns True if the group has options, false otherwise
|
|
133
|
+
*/
|
|
134
|
+
hasGroupOptions(group) {
|
|
135
|
+
return this.groupOptions.has(group);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Sets options for a specific group.
|
|
139
|
+
* @param group - The group key
|
|
140
|
+
* @param options - The options to set
|
|
141
|
+
*/
|
|
142
|
+
setGroupOptions(group, options) {
|
|
143
|
+
this.groupOptions.set(group, options);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Sets rate limit options for a request.
|
|
147
|
+
* @param url - The request URL
|
|
148
|
+
* @param options - The options to set for this group
|
|
149
|
+
*/
|
|
150
|
+
setOptionsForRequest(url, options) {
|
|
151
|
+
const group = this.getGroup(url);
|
|
152
|
+
this.setGroupOptions(group, options);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Updates rate limit options for a request based on standard rate limit headers.
|
|
156
|
+
* @param url - The request URL
|
|
157
|
+
* @param method - The HTTP method
|
|
158
|
+
* @param headers - The response headers containing rate limit information
|
|
159
|
+
*/
|
|
160
|
+
updateFromHeadersForRequest(url, headers) {
|
|
161
|
+
const group = this.getGroup(url);
|
|
162
|
+
this.updateFromHeaders(group, headers);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Updates rate limit options based on standard rate limit headers.
|
|
166
|
+
* @param group - The group key
|
|
167
|
+
* @param headers - The response headers containing rate limit information
|
|
168
|
+
*/
|
|
169
|
+
updateFromHeaders(group, headers) {
|
|
170
|
+
// Get existing group-specific options (not global fallback)
|
|
171
|
+
const currentOptions = this.hasGroupOptions(group)
|
|
172
|
+
? this.groupOptions.get(group)
|
|
173
|
+
: {};
|
|
174
|
+
const newOptions = { ...currentOptions };
|
|
175
|
+
// Parse IETF standard rate limit headers first, then fall back to x-ratelimit headers
|
|
176
|
+
let limit = null;
|
|
177
|
+
let window = null;
|
|
178
|
+
let reset = null;
|
|
179
|
+
// Try IETF standard headers first
|
|
180
|
+
const rateLimitPolicyHeader = headers.get("ratelimit-policy");
|
|
181
|
+
if (rateLimitPolicyHeader) {
|
|
182
|
+
const parsed = parseRateLimitPolicyHeader(rateLimitPolicyHeader);
|
|
183
|
+
if (parsed?.limit) {
|
|
184
|
+
limit = parsed.limit.toString();
|
|
185
|
+
}
|
|
186
|
+
if (parsed?.windowSeconds) {
|
|
187
|
+
window = parsed.windowSeconds.toString();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const rateLimitHeader = headers.get("ratelimit");
|
|
191
|
+
if (rateLimitHeader) {
|
|
192
|
+
const parsed = parseRateLimitHeader(rateLimitHeader);
|
|
193
|
+
if (parsed?.resetSeconds) {
|
|
194
|
+
reset = parsed.resetSeconds.toString();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Fall back to x-ratelimit headers if IETF headers not found
|
|
198
|
+
if (!limit) {
|
|
199
|
+
limit = headers.get("x-ratelimit-limit") ||
|
|
200
|
+
headers.get("x-rate-limit-limit");
|
|
201
|
+
}
|
|
202
|
+
if (!window) {
|
|
203
|
+
window = headers.get("x-ratelimit-window") ||
|
|
204
|
+
headers.get("x-rate-limit-window");
|
|
205
|
+
}
|
|
206
|
+
if (!reset) {
|
|
207
|
+
reset = headers.get("x-ratelimit-reset") ||
|
|
208
|
+
headers.get("x-rate-limit-reset");
|
|
209
|
+
}
|
|
210
|
+
let hasChanges = false;
|
|
211
|
+
// Apply the parsed values
|
|
212
|
+
if (limit) {
|
|
213
|
+
const maxRequests = parseInt(limit, 10);
|
|
214
|
+
if (!isNaN(maxRequests)) {
|
|
215
|
+
newOptions.maxRequests = maxRequests;
|
|
216
|
+
hasChanges = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (window) {
|
|
220
|
+
const windowSeconds = parseInt(window, 10);
|
|
221
|
+
if (!isNaN(windowSeconds)) {
|
|
222
|
+
newOptions.windowSeconds = windowSeconds;
|
|
223
|
+
hasChanges = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (reset) {
|
|
227
|
+
// If no window header, try to calculate from reset time
|
|
228
|
+
const resetTime = parseInt(reset, 10);
|
|
229
|
+
if (!isNaN(resetTime)) {
|
|
230
|
+
const now = Math.floor(Date.now() / 1000);
|
|
231
|
+
const windowSeconds = Math.max(1, resetTime - now);
|
|
232
|
+
newOptions.windowSeconds = windowSeconds;
|
|
233
|
+
hasChanges = true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Update the group options if we found valid headers
|
|
237
|
+
if (hasChanges) {
|
|
238
|
+
this.setGroupOptions(group, newOptions);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Clears all rate limit state.
|
|
243
|
+
*/
|
|
244
|
+
clearAll() {
|
|
245
|
+
this.buckets.clear();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Creates a group generator function that groups requests by domain only (no protocol).
|
|
250
|
+
* @param url - The request URL
|
|
251
|
+
* @returns A string representing the domain without protocol
|
|
252
|
+
*/
|
|
253
|
+
export function groupByDomain(url) {
|
|
254
|
+
try {
|
|
255
|
+
const urlObj = new URL(url);
|
|
256
|
+
return urlObj.hostname;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return url;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Creates an IETF standard RateLimit header value.
|
|
264
|
+
* @param info - The rate limit information
|
|
265
|
+
* @returns The formatted RateLimit header value
|
|
266
|
+
*/
|
|
267
|
+
export function buildRateLimitHeader(info) {
|
|
268
|
+
let headerValue = `"${info.policy}";r=${info.remaining}`;
|
|
269
|
+
if (info.resetSeconds > 0) {
|
|
270
|
+
headerValue += `;t=${info.resetSeconds}`;
|
|
271
|
+
}
|
|
272
|
+
return headerValue;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Creates an IETF standard RateLimit-Policy header value.
|
|
276
|
+
* @param info - The rate limit information
|
|
277
|
+
* @returns The formatted RateLimit-Policy header value
|
|
278
|
+
*/
|
|
279
|
+
export function buildRateLimitPolicyHeader(info) {
|
|
280
|
+
let headerValue = `"${info.policy}";q=${info.limit}`;
|
|
281
|
+
if (info.windowSeconds && info.windowSeconds > 0) {
|
|
282
|
+
headerValue += `;w=${info.windowSeconds}`;
|
|
283
|
+
}
|
|
284
|
+
return headerValue;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Parses an IETF standard RateLimit header value.
|
|
288
|
+
* @param headerValue - The RateLimit header value to parse
|
|
289
|
+
* @returns The parsed rate limit information or null if invalid
|
|
290
|
+
*/
|
|
291
|
+
export function parseRateLimitHeader(headerValue) {
|
|
292
|
+
if (!headerValue)
|
|
293
|
+
return null;
|
|
294
|
+
try {
|
|
295
|
+
const result = {};
|
|
296
|
+
// Extract policy name (quoted string at the beginning)
|
|
297
|
+
const policyMatch = headerValue.match(/^"([^"]+)"/);
|
|
298
|
+
if (policyMatch) {
|
|
299
|
+
result.policy = policyMatch[1];
|
|
300
|
+
}
|
|
301
|
+
// Extract remaining (r parameter)
|
|
302
|
+
const remainingMatch = headerValue.match(/r=(\d+)/);
|
|
303
|
+
if (remainingMatch) {
|
|
304
|
+
result.remaining = parseInt(remainingMatch[1], 10);
|
|
305
|
+
}
|
|
306
|
+
// Extract reset time (t parameter)
|
|
307
|
+
const resetMatch = headerValue.match(/t=(\d+)/);
|
|
308
|
+
if (resetMatch) {
|
|
309
|
+
result.resetSeconds = parseInt(resetMatch[1], 10);
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Parses an IETF standard RateLimit-Policy header value.
|
|
319
|
+
* @param headerValue - The RateLimit-Policy header value to parse
|
|
320
|
+
* @returns The parsed rate limit policy information or null if invalid
|
|
321
|
+
*/
|
|
322
|
+
export function parseRateLimitPolicyHeader(headerValue) {
|
|
323
|
+
if (!headerValue)
|
|
324
|
+
return null;
|
|
325
|
+
try {
|
|
326
|
+
const result = {};
|
|
327
|
+
// Extract policy name (quoted string at the beginning)
|
|
328
|
+
const policyMatch = headerValue.match(/^"([^"]+)"/);
|
|
329
|
+
if (policyMatch) {
|
|
330
|
+
result.policy = policyMatch[1];
|
|
331
|
+
}
|
|
332
|
+
// Extract quota/limit (q parameter)
|
|
333
|
+
const quotaMatch = headerValue.match(/q=(\d+)/);
|
|
334
|
+
if (quotaMatch) {
|
|
335
|
+
result.limit = parseInt(quotaMatch[1], 10);
|
|
336
|
+
}
|
|
337
|
+
// Extract window (w parameter)
|
|
338
|
+
const windowMatch = headerValue.match(/w=(\d+)/);
|
|
339
|
+
if (windowMatch) {
|
|
340
|
+
result.windowSeconds = parseInt(windowMatch[1], 10);
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/license
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Foundatio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@foundatiofx/fetchclient",
|
|
3
|
+
"version": "0.47.0",
|
|
4
|
+
"description": "A typed JSON fetch client with middleware support for Deno, Node and the browser.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"Fetch",
|
|
7
|
+
"Middleware",
|
|
8
|
+
"ProblemDetails",
|
|
9
|
+
"Problem"
|
|
10
|
+
],
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Eric J. Smith",
|
|
13
|
+
"email": "eric@exceptionless.com",
|
|
14
|
+
"url": "https://exceptionless.com"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/FoundatioFx/FetchClient#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/FoundatioFx/FetchClient.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/FoundatioFx/FetchClient/issues"
|
|
24
|
+
},
|
|
25
|
+
"main": "./script/mod.js",
|
|
26
|
+
"module": "./esm/mod.js",
|
|
27
|
+
"types": "./types/mod.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./types/mod.d.ts",
|
|
32
|
+
"default": "./esm/mod.js"
|
|
33
|
+
},
|
|
34
|
+
"require": {
|
|
35
|
+
"types": "./types/mod.d.ts",
|
|
36
|
+
"default": "./script/mod.js"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "node test_runner.js"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.9.0",
|
|
45
|
+
"picocolors": "^1.0.0",
|
|
46
|
+
"zod": "^4.1.4",
|
|
47
|
+
"@deno/shim-deno": "~0.18.0"
|
|
48
|
+
},
|
|
49
|
+
"_generatedBy": "dnt@dev"
|
|
50
|
+
}
|