@http-client-toolkit/core 0.0.1 → 0.1.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 +79 -31
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +11 -1
- package/lib/index.d.ts +11 -1
- package/lib/index.js +79 -27
- package/lib/index.js.map +1 -1
- package/package.json +1 -2
package/lib/index.cjs
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
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
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
-
|
|
9
|
-
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
10
|
-
|
|
11
6
|
var __async = (__this, __arguments, generator) => {
|
|
12
7
|
return new Promise((resolve, reject) => {
|
|
13
8
|
var fulfilled = (value) => {
|
|
@@ -264,7 +259,6 @@ var HttpClient = class {
|
|
|
264
259
|
constructor(stores = {}, options = {}) {
|
|
265
260
|
this.serverCooldowns = /* @__PURE__ */ new Map();
|
|
266
261
|
var _a, _b, _c;
|
|
267
|
-
this._http = axios__default.default.create();
|
|
268
262
|
this.stores = stores;
|
|
269
263
|
this.options = {
|
|
270
264
|
defaultCacheTTL: (_a = options.defaultCacheTTL) != null ? _a : 3600,
|
|
@@ -361,6 +355,15 @@ var HttpClient = class {
|
|
|
361
355
|
if (!headers) {
|
|
362
356
|
return void 0;
|
|
363
357
|
}
|
|
358
|
+
if (headers instanceof Headers) {
|
|
359
|
+
for (const rawName of names) {
|
|
360
|
+
const value = headers.get(rawName);
|
|
361
|
+
if (value !== null) {
|
|
362
|
+
return value;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return void 0;
|
|
366
|
+
}
|
|
364
367
|
for (const rawName of names) {
|
|
365
368
|
const name = rawName.toLowerCase();
|
|
366
369
|
const value = (_a = headers[name]) != null ? _a : headers[rawName];
|
|
@@ -487,17 +490,24 @@ var HttpClient = class {
|
|
|
487
490
|
return __async(this, null, function* () {
|
|
488
491
|
const rateLimit = this.stores.rateLimit;
|
|
489
492
|
const startedAt = Date.now();
|
|
493
|
+
const hasAtomicAcquire = typeof rateLimit.acquire === "function";
|
|
494
|
+
const canProceedNow = () => __async(this, null, function* () {
|
|
495
|
+
if (hasAtomicAcquire) {
|
|
496
|
+
return rateLimit.acquire(resource, priority);
|
|
497
|
+
}
|
|
498
|
+
return rateLimit.canProceed(resource, priority);
|
|
499
|
+
});
|
|
490
500
|
if (this.options.throwOnRateLimit) {
|
|
491
|
-
const canProceed = yield
|
|
501
|
+
const canProceed = yield canProceedNow();
|
|
492
502
|
if (!canProceed) {
|
|
493
503
|
const waitTime = yield rateLimit.getWaitTime(resource, priority);
|
|
494
504
|
throw new Error(
|
|
495
505
|
`Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`
|
|
496
506
|
);
|
|
497
507
|
}
|
|
498
|
-
return;
|
|
508
|
+
return hasAtomicAcquire;
|
|
499
509
|
}
|
|
500
|
-
while (!(yield
|
|
510
|
+
while (!(yield canProceedNow())) {
|
|
501
511
|
const suggestedWaitMs = yield rateLimit.getWaitTime(resource, priority);
|
|
502
512
|
const elapsedMs = Date.now() - startedAt;
|
|
503
513
|
const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;
|
|
@@ -509,22 +519,52 @@ var HttpClient = class {
|
|
|
509
519
|
const waitTime = suggestedWaitMs > 0 ? Math.min(suggestedWaitMs, remainingWaitBudgetMs) : Math.min(25, remainingWaitBudgetMs);
|
|
510
520
|
yield wait(waitTime, signal);
|
|
511
521
|
}
|
|
522
|
+
return hasAtomicAcquire;
|
|
512
523
|
});
|
|
513
524
|
}
|
|
514
525
|
generateClientError(err) {
|
|
515
|
-
var _a, _b
|
|
526
|
+
var _a, _b;
|
|
516
527
|
if (this.options.errorHandler) {
|
|
517
528
|
return this.options.errorHandler(err);
|
|
518
529
|
}
|
|
519
530
|
if (err instanceof HttpClientError) {
|
|
520
531
|
return err;
|
|
521
532
|
}
|
|
522
|
-
const
|
|
523
|
-
const statusCode = (_a =
|
|
524
|
-
const
|
|
525
|
-
const
|
|
533
|
+
const responseError = err;
|
|
534
|
+
const statusCode = typeof ((_a = responseError.response) == null ? void 0 : _a.status) === "number" ? responseError.response.status : void 0;
|
|
535
|
+
const responseData = (_b = responseError.response) == null ? void 0 : _b.data;
|
|
536
|
+
const derivedResponseMessage = typeof responseData === "object" && responseData !== null ? responseData.message : void 0;
|
|
537
|
+
const responseMessage = typeof derivedResponseMessage === "string" ? derivedResponseMessage : void 0;
|
|
538
|
+
const errorMessage = err instanceof Error ? err.message : typeof err.message === "string" ? err.message : "Unknown error";
|
|
539
|
+
const message = `${errorMessage}${responseMessage ? `, ${responseMessage}` : ""}`;
|
|
526
540
|
return new HttpClientError(message, statusCode);
|
|
527
541
|
}
|
|
542
|
+
parseResponseBody(response) {
|
|
543
|
+
return __async(this, null, function* () {
|
|
544
|
+
var _a, _b;
|
|
545
|
+
if (response.status === 204 || response.status === 205) {
|
|
546
|
+
return { data: void 0 };
|
|
547
|
+
}
|
|
548
|
+
const rawBody = yield response.text();
|
|
549
|
+
if (!rawBody) {
|
|
550
|
+
return { data: void 0 };
|
|
551
|
+
}
|
|
552
|
+
const contentType = (_b = (_a = response.headers.get("content-type")) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
|
|
553
|
+
const shouldAttemptJsonParsing = contentType.includes("application/json") || contentType.includes("+json") || rawBody.trimStart().startsWith("{") || rawBody.trimStart().startsWith("[");
|
|
554
|
+
if (!shouldAttemptJsonParsing) {
|
|
555
|
+
return { data: rawBody };
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
const parsed = JSON.parse(rawBody);
|
|
559
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
560
|
+
return { data: parsed };
|
|
561
|
+
}
|
|
562
|
+
return { data: parsed };
|
|
563
|
+
} catch (e) {
|
|
564
|
+
return { data: rawBody };
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
528
568
|
get(_0) {
|
|
529
569
|
return __async(this, arguments, function* (url, options = {}) {
|
|
530
570
|
const { signal, priority = "background" } = options;
|
|
@@ -556,16 +596,29 @@ var HttpClient = class {
|
|
|
556
596
|
yield this.stores.dedupe.register(hash);
|
|
557
597
|
}
|
|
558
598
|
}
|
|
599
|
+
let alreadyRecordedRateLimit = false;
|
|
559
600
|
if (this.stores.rateLimit) {
|
|
560
|
-
yield this.enforceStoreRateLimit(
|
|
601
|
+
alreadyRecordedRateLimit = yield this.enforceStoreRateLimit(
|
|
602
|
+
resource,
|
|
603
|
+
priority,
|
|
604
|
+
signal
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
const response = yield fetch(url, { signal });
|
|
608
|
+
this.applyServerRateLimitHints(url, response.headers, response.status);
|
|
609
|
+
const parsedBody = yield this.parseResponseBody(response);
|
|
610
|
+
if (!response.ok) {
|
|
611
|
+
const error = {
|
|
612
|
+
message: `Request failed with status ${response.status}`,
|
|
613
|
+
response: {
|
|
614
|
+
status: response.status,
|
|
615
|
+
data: parsedBody.data,
|
|
616
|
+
headers: response.headers
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
throw error;
|
|
561
620
|
}
|
|
562
|
-
|
|
563
|
-
this.applyServerRateLimitHints(
|
|
564
|
-
url,
|
|
565
|
-
response.headers,
|
|
566
|
-
response.status
|
|
567
|
-
);
|
|
568
|
-
let data = response.data;
|
|
621
|
+
let data = parsedBody.data;
|
|
569
622
|
if (this.options.responseTransformer && data) {
|
|
570
623
|
data = this.options.responseTransformer(data);
|
|
571
624
|
}
|
|
@@ -573,7 +626,7 @@ var HttpClient = class {
|
|
|
573
626
|
data = this.options.responseHandler(data);
|
|
574
627
|
}
|
|
575
628
|
const result = data;
|
|
576
|
-
if (this.stores.rateLimit) {
|
|
629
|
+
if (this.stores.rateLimit && !alreadyRecordedRateLimit) {
|
|
577
630
|
const rateLimit = this.stores.rateLimit;
|
|
578
631
|
yield rateLimit.record(resource, priority);
|
|
579
632
|
}
|
|
@@ -585,20 +638,15 @@ var HttpClient = class {
|
|
|
585
638
|
}
|
|
586
639
|
return result;
|
|
587
640
|
} catch (error) {
|
|
588
|
-
const axiosError = error;
|
|
589
|
-
if (axiosError.response) {
|
|
590
|
-
this.applyServerRateLimitHints(
|
|
591
|
-
url,
|
|
592
|
-
axiosError.response.headers,
|
|
593
|
-
axiosError.response.status
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
641
|
if (this.stores.dedupe) {
|
|
597
642
|
yield this.stores.dedupe.fail(hash, error);
|
|
598
643
|
}
|
|
599
644
|
if (error instanceof Error && error.name === "AbortError") {
|
|
600
645
|
throw error;
|
|
601
646
|
}
|
|
647
|
+
if (error instanceof HttpClientError) {
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
602
650
|
throw this.generateClientError(error);
|
|
603
651
|
}
|
|
604
652
|
});
|
package/lib/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors/http-client-error.ts","../src/stores/rate-limit-store.ts","../src/stores/request-hasher.ts","../src/stores/rate-limit-config.ts","../src/stores/adaptive-capacity-calculator.ts","../src/http-client/http-client.ts"],"names":["z","createHash","baseUserCapacity","axios"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Base error class for HTTP client errors.\n * Consumers can extend this for domain-specific error handling.\n */\nexport class HttpClientError extends Error {\n public readonly statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = 'HttpClientError';\n this.statusCode = statusCode;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { z } from 'zod';\n\n/**\n * Priority level for API requests\n */\nexport type RequestPriority = 'user' | 'background';\n\n/**\n * Adaptive configuration schema with validation and defaults\n */\nexport const AdaptiveConfigSchema = z\n .object({\n monitoringWindowMs: z\n .number()\n .positive()\n .default(15 * 60 * 1000), // 15 minutes\n highActivityThreshold: z.number().min(0).default(10), // requests per window\n moderateActivityThreshold: z.number().min(0).default(3),\n recalculationIntervalMs: z.number().positive().default(30000), // 30 seconds\n sustainedInactivityThresholdMs: z\n .number()\n .positive()\n .default(30 * 60 * 1000), // 30 minutes\n backgroundPauseOnIncreasingTrend: z.boolean().default(true),\n maxUserScaling: z.number().positive().default(2.0), // don't exceed 2x capacity\n minUserReserved: z.number().min(0).default(5), // requests minimum\n })\n .refine(\n (data) => {\n return data.moderateActivityThreshold < data.highActivityThreshold;\n },\n {\n message:\n 'moderateActivityThreshold must be less than highActivityThreshold',\n },\n );\n\n/**\n * Configuration for adaptive rate limiting\n */\nexport type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;\n\n/**\n * Interface for rate limiting API requests per resource\n */\nexport interface RateLimitStore {\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n */\n record(resource: string): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n }>;\n\n /**\n * Reset rate limits for a resource (useful for testing)\n * @param resource The resource name\n */\n reset(resource: string): Promise<void>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string): Promise<number>;\n}\n\n/**\n * Enhanced interface for adaptive rate limiting stores with priority support\n */\nexport interface AdaptiveRateLimitStore extends RateLimitStore {\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string, priority?: RequestPriority): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n */\n record(resource: string, priority?: RequestPriority): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n adaptive?: {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n recentUserActivity: number;\n reason: string;\n };\n }>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @param priority The priority level of the request (defaults to 'background')\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string, priority?: RequestPriority): Promise<number>;\n}\n","import { createHash } from 'crypto';\n\n/**\n * Creates a consistent hash for API requests to use as cache/dedupe keys\n * @param endpoint The API endpoint\n * @param params The request parameters\n * @returns A SHA-256 hash of the request\n */\nexport function hashRequest(\n endpoint: string,\n params: Record<string, unknown> = {},\n): string {\n const requestString = JSON.stringify({\n endpoint,\n params: sortObject(params),\n });\n\n return createHash('sha256').update(requestString).digest('hex');\n}\n\n/**\n * Normalises and sorts an object for hashing purposes.\n *\n * The ComicVine API transmits all query parameters as strings. To avoid cache\n * misses caused by treating `10` and `'10'` as different values we normalise\n * primitive types (number and boolean) to their string representation **before**\n * sorting. `undefined` values are intentionally kept as `undefined` so that\n * they are dropped by `JSON.stringify`, maintaining the existing behaviour\n * where an omitted parameter and an `undefined` parameter produce the same\n * hash.\n */\nfunction sortObject(obj: unknown): unknown {\n // Handle primitives first\n if (obj === null) {\n return null;\n }\n\n const objType = typeof obj;\n\n if (objType === 'undefined' || objType === 'string') {\n return obj;\n }\n\n if (objType === 'number' || objType === 'boolean') {\n // Convert to string so that 10 and '10' (or true and 'true') hash equally\n return String(obj);\n }\n\n // Recursively process arrays\n if (Array.isArray(obj)) {\n return obj.map(sortObject);\n }\n\n // For objects – sort keys and recurse\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj as Record<string, unknown>).sort();\n\n for (const key of keys) {\n const value = (obj as Record<string, unknown>)[key];\n const normalisedValue = sortObject(value);\n\n // Skip keys whose value normalises to undefined so omitted & undefined match\n if (normalisedValue !== undefined) {\n sorted[key] = normalisedValue;\n }\n }\n\n return sorted;\n}\n","/**\n * Configuration for per-resource rate limiting.\n *\n * This interface is shared by all store implementations (e.g. in-memory,\n * SQLite) so that callers can use a single canonical type.\n */\nexport interface RateLimitConfig {\n /** Number of requests allowed per time window */\n limit: number;\n /** Duration of the window in milliseconds */\n windowMs: number;\n}\n\n/**\n * Default rate-limit window: 60 requests per minute.\n *\n * Store implementations can reference this to avoid duplicating magic numbers.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimitConfig = {\n limit: 60,\n windowMs: 60_000,\n};\n","import { z } from 'zod';\nimport { AdaptiveConfigSchema } from './rate-limit-store.js';\n\ninterface ActivityMetrics {\n recentUserRequests: Array<number>;\n recentBackgroundRequests: Array<number>;\n userActivityTrend: 'increasing' | 'stable' | 'decreasing' | 'none';\n}\n\ninterface DynamicCapacityResult {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n reason: string;\n}\n\n/**\n * Calculates dynamic capacity allocation based on real-time user activity patterns\n */\nexport class AdaptiveCapacityCalculator {\n public readonly config: z.infer<typeof AdaptiveConfigSchema>;\n\n constructor(config: Partial<z.input<typeof AdaptiveConfigSchema>> = {}) {\n // Zod handles validation and applies defaults automatically\n this.config = AdaptiveConfigSchema.parse(config);\n }\n\n calculateDynamicCapacity(\n resource: string,\n totalLimit: number,\n activityMetrics: ActivityMetrics,\n ): DynamicCapacityResult {\n const recentUserActivity = this.getRecentActivity(\n activityMetrics.recentUserRequests,\n );\n const activityTrend = this.calculateActivityTrend(\n activityMetrics.recentUserRequests,\n );\n\n // Strategy 1: High Activity - Pause Background\n if (recentUserActivity >= this.config.highActivityThreshold) {\n const userCapacity = Math.min(\n totalLimit * 0.9,\n Math.floor(totalLimit * 0.5 * this.config.maxUserScaling), // 50% base * scaling factor\n );\n\n return {\n userReserved: userCapacity,\n backgroundMax: totalLimit - userCapacity,\n backgroundPaused:\n this.config.backgroundPauseOnIncreasingTrend &&\n activityTrend === 'increasing',\n reason: `High user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - prioritizing users`,\n };\n }\n\n // Strategy 2: Moderate Activity - Balanced Scaling\n if (recentUserActivity >= this.config.moderateActivityThreshold) {\n const userMultiplier = this.getUserMultiplier(\n recentUserActivity,\n activityTrend,\n );\n const baseUserCapacity = Math.floor(totalLimit * 0.4); // 40% base allocation\n const dynamicUserCapacity = Math.min(\n totalLimit * 0.7,\n baseUserCapacity * userMultiplier,\n );\n\n return {\n userReserved: dynamicUserCapacity,\n backgroundMax: totalLimit - dynamicUserCapacity,\n backgroundPaused: false,\n reason: `Moderate user activity - dynamic scaling (${userMultiplier.toFixed(1)}x user capacity)`,\n };\n }\n\n // Strategy 3: Low/No Activity - Background Scale Up\n if (recentUserActivity === 0) {\n // If there have never been any requests at all (fresh start), use default capacity allocation\n if (\n activityMetrics.recentUserRequests.length === 0 &&\n activityMetrics.recentBackgroundRequests.length === 0\n ) {\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for initial state\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit -\n Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: 'Initial state - default capacity allocation',\n };\n }\n\n // If there have never been user requests (only background), use background scale up\n if (activityMetrics.recentUserRequests.length === 0) {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'No user activity yet - background scale up with minimal user buffer',\n };\n }\n\n // There have been user requests before, check for sustained inactivity\n const sustainedInactivity = this.getSustainedInactivityPeriod(\n activityMetrics.recentUserRequests,\n );\n\n if (sustainedInactivity > this.config.sustainedInactivityThresholdMs) {\n return {\n userReserved: 0, // No reservation - background gets everything!\n backgroundMax: totalLimit, // Full capacity available\n backgroundPaused: false,\n reason: `Sustained zero activity (${Math.floor(sustainedInactivity / 60000)}+ min) - full capacity to background`,\n };\n } else {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'Recent zero activity - background scale up with minimal user buffer',\n };\n }\n }\n\n // Strategy 4: Very Low Activity - Gradual Background Scale Up\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for very low activity\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit - Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: `Low user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - background scale up`,\n };\n }\n\n getRecentActivity(requests: Array<number>): number {\n const cutoff = Date.now() - this.config.monitoringWindowMs;\n return requests.filter((timestamp) => timestamp > cutoff).length;\n }\n\n calculateActivityTrend(\n requests: Array<number>,\n ): 'increasing' | 'stable' | 'decreasing' | 'none' {\n const now = Date.now();\n const windowSize = this.config.monitoringWindowMs / 3; // Use 1/3 of monitoring window for trend\n const recent = requests.filter((t) => t > now - windowSize).length;\n const previous = requests.filter(\n (t) => t > now - 2 * windowSize && t <= now - windowSize,\n ).length;\n\n if (recent === 0 && previous === 0) return 'none';\n if (recent > previous * 1.5) return 'increasing';\n if (recent < previous * 0.5) return 'decreasing';\n return 'stable';\n }\n\n private getUserMultiplier(activity: number, trend: string): number {\n let base = Math.min(\n this.config.maxUserScaling,\n 1 + activity / this.config.highActivityThreshold,\n );\n\n // Adjust based on trend\n if (trend === 'increasing') base *= 1.2;\n if (trend === 'decreasing') base *= 0.8;\n\n return Math.max(1.0, base);\n }\n\n private getSustainedInactivityPeriod(requests: Array<number>): number {\n if (requests.length === 0) {\n // This should not be called when there are no requests (handled above)\n return 0;\n }\n\n const lastRequest = Math.max(...requests);\n return Date.now() - lastRequest;\n }\n}\n\n// Export types for use in other modules\nexport type { ActivityMetrics, DynamicCapacityResult };\n","import axios, { AxiosError } from 'axios';\nimport { HttpClientError } from '../errors/http-client-error.js';\nimport {\n CacheStore,\n DedupeStore,\n RateLimitStore,\n AdaptiveRateLimitStore,\n RequestPriority,\n hashRequest,\n} from '../stores/index.js';\nimport { HttpClientContract } from '../types/index.js';\n\nconst DEFAULT_RATE_LIMIT_HEADER_NAMES = {\n retryAfter: ['retry-after'],\n limit: ['ratelimit-limit', 'x-ratelimit-limit', 'rate-limit-limit'],\n remaining: [\n 'ratelimit-remaining',\n 'x-ratelimit-remaining',\n 'rate-limit-remaining',\n ],\n reset: ['ratelimit-reset', 'x-ratelimit-reset', 'rate-limit-reset'],\n combined: ['ratelimit'],\n} as const;\n\n/**\n * Wait for a specified period while supporting cancellation via AbortSignal.\n *\n * If the signal is aborted before the timeout completes the promise rejects\n * with an `Error` whose name is set to `AbortError`, mimicking DOMException in\n * browser environments without depending on it. This allows callers to use a\n * single `AbortController` for both the rate-limit wait *and* the subsequent\n * HTTP request.\n */\nfunction wait(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n if (signal) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n function onAbort() {\n clearTimeout(timer);\n const err = new Error('Aborted');\n err.name = 'AbortError';\n reject(err);\n }\n\n if (signal) {\n if (signal.aborted) {\n onAbort();\n } else {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n });\n}\n\nexport interface HttpClientStores {\n cache?: CacheStore;\n dedupe?: DedupeStore;\n rateLimit?: RateLimitStore | AdaptiveRateLimitStore;\n}\n\nexport interface HttpClientOptions {\n /**\n * Default cache TTL in seconds\n */\n defaultCacheTTL?: number;\n /**\n * Whether to throw errors on rate limit violations\n */\n throwOnRateLimit?: boolean;\n /**\n * Maximum time to wait for rate limit in milliseconds\n */\n maxWaitTime?: number;\n /**\n * Optional response transformer applied to the raw response data.\n * Use this for converting snake_case to camelCase, etc.\n */\n responseTransformer?: (data: unknown) => unknown;\n /**\n * Optional error handler to convert errors into domain-specific error types.\n * If not provided, a generic HttpClientError is thrown.\n */\n errorHandler?: (error: unknown) => Error;\n /**\n * Optional response validator/handler called after transformation.\n * Use this to inspect the response and throw domain-specific errors\n * based on response content (e.g., API-level error codes).\n */\n responseHandler?: (data: unknown) => unknown;\n /**\n * Configure rate-limit response header names for standards and custom APIs.\n */\n rateLimitHeaders?: {\n retryAfter?: Array<string>;\n limit?: Array<string>;\n remaining?: Array<string>;\n reset?: Array<string>;\n combined?: Array<string>;\n };\n}\n\ninterface RateLimitHeaderConfig {\n retryAfter: Array<string>;\n limit: Array<string>;\n remaining: Array<string>;\n reset: Array<string>;\n combined: Array<string>;\n}\n\nexport class HttpClient implements HttpClientContract {\n private _http;\n private stores: HttpClientStores;\n private serverCooldowns = new Map<string, number>();\n private options: Required<\n Pick<\n HttpClientOptions,\n 'defaultCacheTTL' | 'throwOnRateLimit' | 'maxWaitTime'\n >\n > &\n Pick<\n HttpClientOptions,\n 'responseTransformer' | 'errorHandler' | 'responseHandler'\n > & {\n rateLimitHeaders: RateLimitHeaderConfig;\n };\n\n constructor(stores: HttpClientStores = {}, options: HttpClientOptions = {}) {\n this._http = axios.create();\n this.stores = stores;\n this.options = {\n defaultCacheTTL: options.defaultCacheTTL ?? 3600,\n throwOnRateLimit: options.throwOnRateLimit ?? true,\n maxWaitTime: options.maxWaitTime ?? 60000,\n responseTransformer: options.responseTransformer,\n errorHandler: options.errorHandler,\n responseHandler: options.responseHandler,\n rateLimitHeaders: this.normalizeRateLimitHeaders(\n options.rateLimitHeaders,\n ),\n };\n }\n\n private normalizeRateLimitHeaders(\n customHeaders?: HttpClientOptions['rateLimitHeaders'],\n ): RateLimitHeaderConfig {\n return {\n retryAfter: this.normalizeHeaderNames(\n customHeaders?.retryAfter,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.retryAfter,\n ),\n limit: this.normalizeHeaderNames(\n customHeaders?.limit,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.limit,\n ),\n remaining: this.normalizeHeaderNames(\n customHeaders?.remaining,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.remaining,\n ),\n reset: this.normalizeHeaderNames(\n customHeaders?.reset,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.reset,\n ),\n combined: this.normalizeHeaderNames(\n customHeaders?.combined,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.combined,\n ),\n };\n }\n\n private normalizeHeaderNames(\n providedNames: Array<string> | undefined,\n defaultNames: ReadonlyArray<string>,\n ): Array<string> {\n if (!providedNames || providedNames.length === 0) {\n return [...defaultNames];\n }\n\n const customNames = providedNames\n .map((name) => name.trim().toLowerCase())\n .filter(Boolean);\n\n if (customNames.length === 0) {\n return [...defaultNames];\n }\n\n return [...new Set([...customNames, ...defaultNames])];\n }\n\n /**\n * Infer the resource name from the endpoint URL\n * @param url The full URL or endpoint path\n * @returns The resource name for rate limiting\n */\n private inferResource(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use the first meaningful path segment as the resource name\n const segments = urlObj.pathname.split('/').filter(Boolean);\n return segments[segments.length - 1] || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Extract endpoint and params from URL for request hashing\n * @param url The full URL\n * @returns Object with endpoint and params for hashing\n */\n private parseUrlForHashing(url: string): {\n endpoint: string;\n params: Record<string, unknown>;\n } {\n const urlObj = new URL(url);\n const endpoint = `${urlObj.origin}${urlObj.pathname}`;\n const params: Record<string, unknown> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n const existing = params[key];\n\n // Keep repeated query keys as arrays so semantically distinct URLs like\n // `?tag=a&tag=b` and `?tag=b` do not hash to the same cache/dedupe key.\n if (existing === undefined) {\n params[key] = value;\n return;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n return;\n }\n\n params[key] = [existing, value];\n });\n\n return { endpoint, params };\n }\n\n private getOriginScope(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return 'unknown';\n }\n }\n\n private getHeaderValue(\n headers: Record<string, unknown> | undefined,\n names: Array<string>,\n ): string | undefined {\n if (!headers) {\n return undefined;\n }\n\n for (const rawName of names) {\n const name = rawName.toLowerCase();\n const value = headers[name] ?? headers[rawName];\n\n if (typeof value === 'string') {\n return value;\n }\n\n if (Array.isArray(value) && value.length > 0) {\n const first = value.find((entry) => typeof entry === 'string');\n if (typeof first === 'string') {\n return first;\n }\n }\n }\n\n return undefined;\n }\n\n private parseIntegerHeader(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const parsed = Number.parseInt(value.trim(), 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return undefined;\n }\n\n return parsed;\n }\n\n private parseRetryAfterMs(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const numeric = Number.parseInt(value.trim(), 10);\n if (Number.isFinite(numeric) && numeric >= 0) {\n return numeric * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (!Number.isFinite(dateMs)) {\n return undefined;\n }\n\n return Math.max(0, dateMs - Date.now());\n }\n\n private parseResetMs(value: string | undefined): number | undefined {\n const parsed = this.parseIntegerHeader(value);\n if (parsed === undefined) {\n return undefined;\n }\n\n if (parsed === 0) {\n return 0;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n\n if (parsed > nowSeconds + 1) {\n return Math.max(0, (parsed - nowSeconds) * 1000);\n }\n\n return parsed * 1000;\n }\n\n private parseCombinedRateLimitHeader(value: string | undefined): {\n remaining?: number;\n resetMs?: number;\n } {\n if (!value) {\n return {};\n }\n\n const remainingMatch = value.match(/(?:^|[;,])\\s*r\\s*=\\s*(\\d+)/i);\n const resetMatch = value.match(/(?:^|[;,])\\s*t\\s*=\\s*(\\d+)/i);\n\n return {\n remaining: remainingMatch\n ? this.parseIntegerHeader(remainingMatch[1])\n : undefined,\n resetMs: resetMatch ? this.parseResetMs(resetMatch[1]) : undefined,\n };\n }\n\n private applyServerRateLimitHints(\n url: string,\n headers: Record<string, unknown> | undefined,\n statusCode?: number,\n ): void {\n if (!headers) {\n return;\n }\n\n const config = this.options.rateLimitHeaders;\n const retryAfterRaw = this.getHeaderValue(headers, config.retryAfter);\n const resetRaw = this.getHeaderValue(headers, config.reset);\n const remainingRaw = this.getHeaderValue(headers, config.remaining);\n const combinedRaw = this.getHeaderValue(headers, config.combined);\n\n const retryAfterMs = this.parseRetryAfterMs(retryAfterRaw);\n const resetMs = this.parseResetMs(resetRaw);\n const remaining = this.parseIntegerHeader(remainingRaw);\n const combined = this.parseCombinedRateLimitHeader(combinedRaw);\n\n const effectiveRemaining = remaining ?? combined.remaining;\n const effectiveResetMs = resetMs ?? combined.resetMs;\n const hasRateLimitErrorStatus = statusCode === 429 || statusCode === 503;\n\n let waitMs: number | undefined;\n\n if (retryAfterMs !== undefined) {\n waitMs = retryAfterMs;\n } else if (\n effectiveResetMs !== undefined &&\n (hasRateLimitErrorStatus ||\n (effectiveRemaining !== undefined && effectiveRemaining <= 0))\n ) {\n waitMs = effectiveResetMs;\n }\n\n if (waitMs === undefined || waitMs <= 0) {\n return;\n }\n\n const scope = this.getOriginScope(url);\n this.serverCooldowns.set(scope, Date.now() + waitMs);\n }\n\n private async enforceServerCooldown(\n url: string,\n signal?: AbortSignal,\n ): Promise<void> {\n const scope = this.getOriginScope(url);\n const startedAt = Date.now();\n\n // Re-check cooldown after each sleep so we never proceed while a server\n // cooldown is still active. This avoids bypassing limits when cooldown\n // duration is longer than maxWaitTime.\n while (true) {\n const cooldownUntil = this.serverCooldowns.get(scope);\n if (!cooldownUntil) {\n return;\n }\n\n const waitMs = cooldownUntil - Date.now();\n if (waitMs <= 0) {\n this.serverCooldowns.delete(scope);\n return;\n }\n\n if (this.options.throwOnRateLimit) {\n throw new Error(\n `Rate limit exceeded for origin '${scope}'. Wait ${waitMs}ms before retrying.`,\n );\n }\n\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for origin '${scope}'.`,\n );\n }\n\n await wait(Math.min(waitMs, remainingWaitBudgetMs), signal);\n }\n }\n\n private async enforceStoreRateLimit(\n resource: string,\n priority: RequestPriority,\n signal?: AbortSignal,\n ): Promise<void> {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n const startedAt = Date.now();\n\n if (this.options.throwOnRateLimit) {\n const canProceed = await rateLimit.canProceed(resource, priority);\n if (!canProceed) {\n const waitTime = await rateLimit.getWaitTime(resource, priority);\n throw new Error(\n `Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`,\n );\n }\n return;\n }\n\n // Keep polling + waiting until the store explicitly allows the request or\n // we exhaust maxWaitTime. A single one-off sleep can otherwise let a request\n // through while still over limit.\n while (!(await rateLimit.canProceed(resource, priority))) {\n const suggestedWaitMs = await rateLimit.getWaitTime(resource, priority);\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for resource '${resource}'.`,\n );\n }\n\n // If a store reports \"blocked\" but no wait time, use a tiny backoff to\n // avoid a tight CPU loop while still converging quickly.\n const waitTime =\n suggestedWaitMs > 0\n ? Math.min(suggestedWaitMs, remainingWaitBudgetMs)\n : Math.min(25, remainingWaitBudgetMs);\n\n await wait(waitTime, signal);\n }\n }\n\n private generateClientError(err: unknown): Error {\n // If a custom error handler is provided, use it\n if (this.options.errorHandler) {\n return this.options.errorHandler(err);\n }\n\n if (err instanceof HttpClientError) {\n return err;\n }\n\n const error = err as AxiosError<{ message?: string }>;\n const statusCode = error.response?.status;\n const errorMessage = error.response?.data?.message;\n const message = `${error.message}${errorMessage ? `, ${errorMessage}` : ''}`;\n\n return new HttpClientError(message, statusCode);\n }\n\n async get<Result>(\n url: string,\n options: { signal?: AbortSignal; priority?: RequestPriority } = {},\n ): Promise<Result> {\n const { signal, priority = 'background' } = options;\n const { endpoint, params } = this.parseUrlForHashing(url);\n const hash = hashRequest(endpoint, params);\n const resource = this.inferResource(url);\n\n try {\n await this.enforceServerCooldown(url, signal);\n\n // 1. Cache - check for cached response\n if (this.stores.cache) {\n const cachedResult = await this.stores.cache.get(hash);\n if (cachedResult !== undefined) {\n return cachedResult as Result;\n }\n }\n\n // 2. Deduplication - check for in-progress request\n if (this.stores.dedupe) {\n const existingResult = await this.stores.dedupe.waitFor(hash);\n if (existingResult !== undefined) {\n return existingResult as Result;\n }\n\n if (this.stores.dedupe.registerOrJoin) {\n const registration = await this.stores.dedupe.registerOrJoin(hash);\n\n if (!registration.isOwner) {\n const joinedResult = await this.stores.dedupe.waitFor(hash);\n if (joinedResult !== undefined) {\n return joinedResult as Result;\n }\n }\n } else {\n await this.stores.dedupe.register(hash);\n }\n }\n\n // 3. Rate limiting - check if request can proceed\n if (this.stores.rateLimit) {\n await this.enforceStoreRateLimit(resource, priority, signal);\n }\n\n // 4. Execute the actual HTTP request\n const response = await this._http.get(url, { signal });\n this.applyServerRateLimitHints(\n url,\n response.headers as Record<string, unknown>,\n response.status,\n );\n\n // 5. Apply response transformer if provided\n let data = response.data;\n if (this.options.responseTransformer && data) {\n data = this.options.responseTransformer(data);\n }\n\n // 6. Apply response handler if provided (for domain-specific validation)\n if (this.options.responseHandler) {\n data = this.options.responseHandler(data);\n }\n\n const result = data as Result;\n\n // 7. Record the request for rate limiting\n if (this.stores.rateLimit) {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n await rateLimit.record(resource, priority);\n }\n\n // 8. Cache the result\n if (this.stores.cache) {\n await this.stores.cache.set(hash, result, this.options.defaultCacheTTL);\n }\n\n // 9. Mark deduplication as complete\n if (this.stores.dedupe) {\n await this.stores.dedupe.complete(hash, result);\n }\n\n return result;\n } catch (error) {\n const axiosError = error as AxiosError;\n if (axiosError.response) {\n this.applyServerRateLimitHints(\n url,\n axiosError.response.headers as Record<string, unknown>,\n axiosError.response.status,\n );\n }\n\n // Mark deduplication as failed\n if (this.stores.dedupe) {\n await this.stores.dedupe.fail(hash, error as Error);\n }\n\n // Allow callers to detect aborts distinctly – do not wrap AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n throw error;\n }\n\n throw this.generateClientError(error);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors/http-client-error.ts","../src/stores/rate-limit-store.ts","../src/stores/request-hasher.ts","../src/stores/rate-limit-config.ts","../src/stores/adaptive-capacity-calculator.ts","../src/http-client/http-client.ts"],"names":["z","createHash","baseUserCapacity"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Base error class for HTTP client errors.\n * Consumers can extend this for domain-specific error handling.\n */\nexport class HttpClientError extends Error {\n public readonly statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = 'HttpClientError';\n this.statusCode = statusCode;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { z } from 'zod';\n\n/**\n * Priority level for API requests\n */\nexport type RequestPriority = 'user' | 'background';\n\n/**\n * Adaptive configuration schema with validation and defaults\n */\nexport const AdaptiveConfigSchema = z\n .object({\n monitoringWindowMs: z\n .number()\n .positive()\n .default(15 * 60 * 1000), // 15 minutes\n highActivityThreshold: z.number().min(0).default(10), // requests per window\n moderateActivityThreshold: z.number().min(0).default(3),\n recalculationIntervalMs: z.number().positive().default(30000), // 30 seconds\n sustainedInactivityThresholdMs: z\n .number()\n .positive()\n .default(30 * 60 * 1000), // 30 minutes\n backgroundPauseOnIncreasingTrend: z.boolean().default(true),\n maxUserScaling: z.number().positive().default(2.0), // don't exceed 2x capacity\n minUserReserved: z.number().min(0).default(5), // requests minimum\n })\n .refine(\n (data) => {\n return data.moderateActivityThreshold < data.highActivityThreshold;\n },\n {\n message:\n 'moderateActivityThreshold must be less than highActivityThreshold',\n },\n );\n\n/**\n * Configuration for adaptive rate limiting\n */\nexport type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;\n\n/**\n * Interface for rate limiting API requests per resource\n */\nexport interface RateLimitStore {\n /**\n * Atomically acquire capacity for a request if the implementation supports it.\n * When present and returning true, callers should treat the request as already\n * recorded for rate-limit accounting.\n */\n acquire?(resource: string): Promise<boolean>;\n\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n */\n record(resource: string): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n }>;\n\n /**\n * Reset rate limits for a resource (useful for testing)\n * @param resource The resource name\n */\n reset(resource: string): Promise<void>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string): Promise<number>;\n}\n\n/**\n * Enhanced interface for adaptive rate limiting stores with priority support\n */\nexport interface AdaptiveRateLimitStore extends RateLimitStore {\n /**\n * Atomically acquire capacity for a request if the implementation supports it.\n */\n acquire?(resource: string, priority?: RequestPriority): Promise<boolean>;\n\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string, priority?: RequestPriority): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n */\n record(resource: string, priority?: RequestPriority): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n adaptive?: {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n recentUserActivity: number;\n reason: string;\n };\n }>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @param priority The priority level of the request (defaults to 'background')\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string, priority?: RequestPriority): Promise<number>;\n}\n","import { createHash } from 'crypto';\n\n/**\n * Creates a consistent hash for API requests to use as cache/dedupe keys\n * @param endpoint The API endpoint\n * @param params The request parameters\n * @returns A SHA-256 hash of the request\n */\nexport function hashRequest(\n endpoint: string,\n params: Record<string, unknown> = {},\n): string {\n const requestString = JSON.stringify({\n endpoint,\n params: sortObject(params),\n });\n\n return createHash('sha256').update(requestString).digest('hex');\n}\n\n/**\n * Normalises and sorts an object for hashing purposes.\n *\n * The ComicVine API transmits all query parameters as strings. To avoid cache\n * misses caused by treating `10` and `'10'` as different values we normalise\n * primitive types (number and boolean) to their string representation **before**\n * sorting. `undefined` values are intentionally kept as `undefined` so that\n * they are dropped by `JSON.stringify`, maintaining the existing behaviour\n * where an omitted parameter and an `undefined` parameter produce the same\n * hash.\n */\nfunction sortObject(obj: unknown): unknown {\n // Handle primitives first\n if (obj === null) {\n return null;\n }\n\n const objType = typeof obj;\n\n if (objType === 'undefined' || objType === 'string') {\n return obj;\n }\n\n if (objType === 'number' || objType === 'boolean') {\n // Convert to string so that 10 and '10' (or true and 'true') hash equally\n return String(obj);\n }\n\n // Recursively process arrays\n if (Array.isArray(obj)) {\n return obj.map(sortObject);\n }\n\n // For objects – sort keys and recurse\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj as Record<string, unknown>).sort();\n\n for (const key of keys) {\n const value = (obj as Record<string, unknown>)[key];\n const normalisedValue = sortObject(value);\n\n // Skip keys whose value normalises to undefined so omitted & undefined match\n if (normalisedValue !== undefined) {\n sorted[key] = normalisedValue;\n }\n }\n\n return sorted;\n}\n","/**\n * Configuration for per-resource rate limiting.\n *\n * This interface is shared by all store implementations (e.g. in-memory,\n * SQLite) so that callers can use a single canonical type.\n */\nexport interface RateLimitConfig {\n /** Number of requests allowed per time window */\n limit: number;\n /** Duration of the window in milliseconds */\n windowMs: number;\n}\n\n/**\n * Default rate-limit window: 60 requests per minute.\n *\n * Store implementations can reference this to avoid duplicating magic numbers.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimitConfig = {\n limit: 60,\n windowMs: 60_000,\n};\n","import { z } from 'zod';\nimport { AdaptiveConfigSchema } from './rate-limit-store.js';\n\ninterface ActivityMetrics {\n recentUserRequests: Array<number>;\n recentBackgroundRequests: Array<number>;\n userActivityTrend: 'increasing' | 'stable' | 'decreasing' | 'none';\n}\n\ninterface DynamicCapacityResult {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n reason: string;\n}\n\n/**\n * Calculates dynamic capacity allocation based on real-time user activity patterns\n */\nexport class AdaptiveCapacityCalculator {\n public readonly config: z.infer<typeof AdaptiveConfigSchema>;\n\n constructor(config: Partial<z.input<typeof AdaptiveConfigSchema>> = {}) {\n // Zod handles validation and applies defaults automatically\n this.config = AdaptiveConfigSchema.parse(config);\n }\n\n calculateDynamicCapacity(\n resource: string,\n totalLimit: number,\n activityMetrics: ActivityMetrics,\n ): DynamicCapacityResult {\n const recentUserActivity = this.getRecentActivity(\n activityMetrics.recentUserRequests,\n );\n const activityTrend = this.calculateActivityTrend(\n activityMetrics.recentUserRequests,\n );\n\n // Strategy 1: High Activity - Pause Background\n if (recentUserActivity >= this.config.highActivityThreshold) {\n const userCapacity = Math.min(\n totalLimit * 0.9,\n Math.floor(totalLimit * 0.5 * this.config.maxUserScaling), // 50% base * scaling factor\n );\n\n return {\n userReserved: userCapacity,\n backgroundMax: totalLimit - userCapacity,\n backgroundPaused:\n this.config.backgroundPauseOnIncreasingTrend &&\n activityTrend === 'increasing',\n reason: `High user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - prioritizing users`,\n };\n }\n\n // Strategy 2: Moderate Activity - Balanced Scaling\n if (recentUserActivity >= this.config.moderateActivityThreshold) {\n const userMultiplier = this.getUserMultiplier(\n recentUserActivity,\n activityTrend,\n );\n const baseUserCapacity = Math.floor(totalLimit * 0.4); // 40% base allocation\n const dynamicUserCapacity = Math.min(\n totalLimit * 0.7,\n baseUserCapacity * userMultiplier,\n );\n\n return {\n userReserved: dynamicUserCapacity,\n backgroundMax: totalLimit - dynamicUserCapacity,\n backgroundPaused: false,\n reason: `Moderate user activity - dynamic scaling (${userMultiplier.toFixed(1)}x user capacity)`,\n };\n }\n\n // Strategy 3: Low/No Activity - Background Scale Up\n if (recentUserActivity === 0) {\n // If there have never been any requests at all (fresh start), use default capacity allocation\n if (\n activityMetrics.recentUserRequests.length === 0 &&\n activityMetrics.recentBackgroundRequests.length === 0\n ) {\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for initial state\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit -\n Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: 'Initial state - default capacity allocation',\n };\n }\n\n // If there have never been user requests (only background), use background scale up\n if (activityMetrics.recentUserRequests.length === 0) {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'No user activity yet - background scale up with minimal user buffer',\n };\n }\n\n // There have been user requests before, check for sustained inactivity\n const sustainedInactivity = this.getSustainedInactivityPeriod(\n activityMetrics.recentUserRequests,\n );\n\n if (sustainedInactivity > this.config.sustainedInactivityThresholdMs) {\n return {\n userReserved: 0, // No reservation - background gets everything!\n backgroundMax: totalLimit, // Full capacity available\n backgroundPaused: false,\n reason: `Sustained zero activity (${Math.floor(sustainedInactivity / 60000)}+ min) - full capacity to background`,\n };\n } else {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'Recent zero activity - background scale up with minimal user buffer',\n };\n }\n }\n\n // Strategy 4: Very Low Activity - Gradual Background Scale Up\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for very low activity\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit - Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: `Low user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - background scale up`,\n };\n }\n\n getRecentActivity(requests: Array<number>): number {\n const cutoff = Date.now() - this.config.monitoringWindowMs;\n return requests.filter((timestamp) => timestamp > cutoff).length;\n }\n\n calculateActivityTrend(\n requests: Array<number>,\n ): 'increasing' | 'stable' | 'decreasing' | 'none' {\n const now = Date.now();\n const windowSize = this.config.monitoringWindowMs / 3; // Use 1/3 of monitoring window for trend\n const recent = requests.filter((t) => t > now - windowSize).length;\n const previous = requests.filter(\n (t) => t > now - 2 * windowSize && t <= now - windowSize,\n ).length;\n\n if (recent === 0 && previous === 0) return 'none';\n if (recent > previous * 1.5) return 'increasing';\n if (recent < previous * 0.5) return 'decreasing';\n return 'stable';\n }\n\n private getUserMultiplier(activity: number, trend: string): number {\n let base = Math.min(\n this.config.maxUserScaling,\n 1 + activity / this.config.highActivityThreshold,\n );\n\n // Adjust based on trend\n if (trend === 'increasing') base *= 1.2;\n if (trend === 'decreasing') base *= 0.8;\n\n return Math.max(1.0, base);\n }\n\n private getSustainedInactivityPeriod(requests: Array<number>): number {\n if (requests.length === 0) {\n // This should not be called when there are no requests (handled above)\n return 0;\n }\n\n const lastRequest = Math.max(...requests);\n return Date.now() - lastRequest;\n }\n}\n\n// Export types for use in other modules\nexport type { ActivityMetrics, DynamicCapacityResult };\n","import { HttpClientError } from '../errors/http-client-error.js';\nimport {\n CacheStore,\n DedupeStore,\n RateLimitStore,\n AdaptiveRateLimitStore,\n RequestPriority,\n hashRequest,\n} from '../stores/index.js';\nimport { HttpClientContract } from '../types/index.js';\n\nconst DEFAULT_RATE_LIMIT_HEADER_NAMES = {\n retryAfter: ['retry-after'],\n limit: ['ratelimit-limit', 'x-ratelimit-limit', 'rate-limit-limit'],\n remaining: [\n 'ratelimit-remaining',\n 'x-ratelimit-remaining',\n 'rate-limit-remaining',\n ],\n reset: ['ratelimit-reset', 'x-ratelimit-reset', 'rate-limit-reset'],\n combined: ['ratelimit'],\n} as const;\n\n/**\n * Wait for a specified period while supporting cancellation via AbortSignal.\n *\n * If the signal is aborted before the timeout completes the promise rejects\n * with an `Error` whose name is set to `AbortError`, mimicking DOMException in\n * browser environments without depending on it. This allows callers to use a\n * single `AbortController` for both the rate-limit wait *and* the subsequent\n * HTTP request.\n */\nfunction wait(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n if (signal) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n function onAbort() {\n clearTimeout(timer);\n const err = new Error('Aborted');\n err.name = 'AbortError';\n reject(err);\n }\n\n if (signal) {\n if (signal.aborted) {\n onAbort();\n } else {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n });\n}\n\nexport interface HttpClientStores {\n cache?: CacheStore;\n dedupe?: DedupeStore;\n rateLimit?: RateLimitStore | AdaptiveRateLimitStore;\n}\n\nexport interface HttpClientOptions {\n /**\n * Default cache TTL in seconds\n */\n defaultCacheTTL?: number;\n /**\n * Whether to throw errors on rate limit violations\n */\n throwOnRateLimit?: boolean;\n /**\n * Maximum time to wait for rate limit in milliseconds\n */\n maxWaitTime?: number;\n /**\n * Optional response transformer applied to the raw response data.\n * Use this for converting snake_case to camelCase, etc.\n */\n responseTransformer?: (data: unknown) => unknown;\n /**\n * Optional error handler to convert errors into domain-specific error types.\n * If not provided, a generic HttpClientError is thrown.\n */\n errorHandler?: (error: unknown) => Error;\n /**\n * Optional response validator/handler called after transformation.\n * Use this to inspect the response and throw domain-specific errors\n * based on response content (e.g., API-level error codes).\n */\n responseHandler?: (data: unknown) => unknown;\n /**\n * Configure rate-limit response header names for standards and custom APIs.\n */\n rateLimitHeaders?: {\n retryAfter?: Array<string>;\n limit?: Array<string>;\n remaining?: Array<string>;\n reset?: Array<string>;\n combined?: Array<string>;\n };\n}\n\ninterface RateLimitHeaderConfig {\n retryAfter: Array<string>;\n limit: Array<string>;\n remaining: Array<string>;\n reset: Array<string>;\n combined: Array<string>;\n}\n\ninterface ParsedResponseBody {\n data: unknown;\n}\n\ntype ErrorWithResponse = {\n message: string;\n response: {\n status: number;\n data: unknown;\n headers: Headers;\n };\n};\n\nexport class HttpClient implements HttpClientContract {\n private stores: HttpClientStores;\n private serverCooldowns = new Map<string, number>();\n private options: Required<\n Pick<\n HttpClientOptions,\n 'defaultCacheTTL' | 'throwOnRateLimit' | 'maxWaitTime'\n >\n > &\n Pick<\n HttpClientOptions,\n 'responseTransformer' | 'errorHandler' | 'responseHandler'\n > & {\n rateLimitHeaders: RateLimitHeaderConfig;\n };\n\n constructor(stores: HttpClientStores = {}, options: HttpClientOptions = {}) {\n this.stores = stores;\n this.options = {\n defaultCacheTTL: options.defaultCacheTTL ?? 3600,\n throwOnRateLimit: options.throwOnRateLimit ?? true,\n maxWaitTime: options.maxWaitTime ?? 60000,\n responseTransformer: options.responseTransformer,\n errorHandler: options.errorHandler,\n responseHandler: options.responseHandler,\n rateLimitHeaders: this.normalizeRateLimitHeaders(\n options.rateLimitHeaders,\n ),\n };\n }\n\n private normalizeRateLimitHeaders(\n customHeaders?: HttpClientOptions['rateLimitHeaders'],\n ): RateLimitHeaderConfig {\n return {\n retryAfter: this.normalizeHeaderNames(\n customHeaders?.retryAfter,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.retryAfter,\n ),\n limit: this.normalizeHeaderNames(\n customHeaders?.limit,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.limit,\n ),\n remaining: this.normalizeHeaderNames(\n customHeaders?.remaining,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.remaining,\n ),\n reset: this.normalizeHeaderNames(\n customHeaders?.reset,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.reset,\n ),\n combined: this.normalizeHeaderNames(\n customHeaders?.combined,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.combined,\n ),\n };\n }\n\n private normalizeHeaderNames(\n providedNames: Array<string> | undefined,\n defaultNames: ReadonlyArray<string>,\n ): Array<string> {\n if (!providedNames || providedNames.length === 0) {\n return [...defaultNames];\n }\n\n const customNames = providedNames\n .map((name) => name.trim().toLowerCase())\n .filter(Boolean);\n\n if (customNames.length === 0) {\n return [...defaultNames];\n }\n\n return [...new Set([...customNames, ...defaultNames])];\n }\n\n /**\n * Infer the resource name from the endpoint URL\n * @param url The full URL or endpoint path\n * @returns The resource name for rate limiting\n */\n private inferResource(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use the first meaningful path segment as the resource name\n const segments = urlObj.pathname.split('/').filter(Boolean);\n return segments[segments.length - 1] || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Extract endpoint and params from URL for request hashing\n * @param url The full URL\n * @returns Object with endpoint and params for hashing\n */\n private parseUrlForHashing(url: string): {\n endpoint: string;\n params: Record<string, unknown>;\n } {\n const urlObj = new URL(url);\n const endpoint = `${urlObj.origin}${urlObj.pathname}`;\n const params: Record<string, unknown> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n const existing = params[key];\n\n // Keep repeated query keys as arrays so semantically distinct URLs like\n // `?tag=a&tag=b` and `?tag=b` do not hash to the same cache/dedupe key.\n if (existing === undefined) {\n params[key] = value;\n return;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n return;\n }\n\n params[key] = [existing, value];\n });\n\n return { endpoint, params };\n }\n\n private getOriginScope(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return 'unknown';\n }\n }\n\n private getHeaderValue(\n headers: Headers | Record<string, unknown> | undefined,\n names: Array<string>,\n ): string | undefined {\n if (!headers) {\n return undefined;\n }\n\n if (headers instanceof Headers) {\n for (const rawName of names) {\n const value = headers.get(rawName);\n if (value !== null) {\n return value;\n }\n }\n return undefined;\n }\n\n for (const rawName of names) {\n const name = rawName.toLowerCase();\n const value = headers[name] ?? headers[rawName];\n\n if (typeof value === 'string') {\n return value;\n }\n\n if (Array.isArray(value) && value.length > 0) {\n const first = value.find((entry) => typeof entry === 'string');\n if (typeof first === 'string') {\n return first;\n }\n }\n }\n\n return undefined;\n }\n\n private parseIntegerHeader(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const parsed = Number.parseInt(value.trim(), 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return undefined;\n }\n\n return parsed;\n }\n\n private parseRetryAfterMs(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const numeric = Number.parseInt(value.trim(), 10);\n if (Number.isFinite(numeric) && numeric >= 0) {\n return numeric * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (!Number.isFinite(dateMs)) {\n return undefined;\n }\n\n return Math.max(0, dateMs - Date.now());\n }\n\n private parseResetMs(value: string | undefined): number | undefined {\n const parsed = this.parseIntegerHeader(value);\n if (parsed === undefined) {\n return undefined;\n }\n\n if (parsed === 0) {\n return 0;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n\n if (parsed > nowSeconds + 1) {\n return Math.max(0, (parsed - nowSeconds) * 1000);\n }\n\n return parsed * 1000;\n }\n\n private parseCombinedRateLimitHeader(value: string | undefined): {\n remaining?: number;\n resetMs?: number;\n } {\n if (!value) {\n return {};\n }\n\n const remainingMatch = value.match(/(?:^|[;,])\\s*r\\s*=\\s*(\\d+)/i);\n const resetMatch = value.match(/(?:^|[;,])\\s*t\\s*=\\s*(\\d+)/i);\n\n return {\n remaining: remainingMatch\n ? this.parseIntegerHeader(remainingMatch[1])\n : undefined,\n resetMs: resetMatch ? this.parseResetMs(resetMatch[1]) : undefined,\n };\n }\n\n private applyServerRateLimitHints(\n url: string,\n headers: Headers | Record<string, unknown> | undefined,\n statusCode?: number,\n ): void {\n if (!headers) {\n return;\n }\n\n const config = this.options.rateLimitHeaders;\n const retryAfterRaw = this.getHeaderValue(headers, config.retryAfter);\n const resetRaw = this.getHeaderValue(headers, config.reset);\n const remainingRaw = this.getHeaderValue(headers, config.remaining);\n const combinedRaw = this.getHeaderValue(headers, config.combined);\n\n const retryAfterMs = this.parseRetryAfterMs(retryAfterRaw);\n const resetMs = this.parseResetMs(resetRaw);\n const remaining = this.parseIntegerHeader(remainingRaw);\n const combined = this.parseCombinedRateLimitHeader(combinedRaw);\n\n const effectiveRemaining = remaining ?? combined.remaining;\n const effectiveResetMs = resetMs ?? combined.resetMs;\n const hasRateLimitErrorStatus = statusCode === 429 || statusCode === 503;\n\n let waitMs: number | undefined;\n\n if (retryAfterMs !== undefined) {\n waitMs = retryAfterMs;\n } else if (\n effectiveResetMs !== undefined &&\n (hasRateLimitErrorStatus ||\n (effectiveRemaining !== undefined && effectiveRemaining <= 0))\n ) {\n waitMs = effectiveResetMs;\n }\n\n if (waitMs === undefined || waitMs <= 0) {\n return;\n }\n\n const scope = this.getOriginScope(url);\n this.serverCooldowns.set(scope, Date.now() + waitMs);\n }\n\n private async enforceServerCooldown(\n url: string,\n signal?: AbortSignal,\n ): Promise<void> {\n const scope = this.getOriginScope(url);\n const startedAt = Date.now();\n\n // Re-check cooldown after each sleep so we never proceed while a server\n // cooldown is still active. This avoids bypassing limits when cooldown\n // duration is longer than maxWaitTime.\n while (true) {\n const cooldownUntil = this.serverCooldowns.get(scope);\n if (!cooldownUntil) {\n return;\n }\n\n const waitMs = cooldownUntil - Date.now();\n if (waitMs <= 0) {\n this.serverCooldowns.delete(scope);\n return;\n }\n\n if (this.options.throwOnRateLimit) {\n throw new Error(\n `Rate limit exceeded for origin '${scope}'. Wait ${waitMs}ms before retrying.`,\n );\n }\n\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for origin '${scope}'.`,\n );\n }\n\n await wait(Math.min(waitMs, remainingWaitBudgetMs), signal);\n }\n }\n\n private async enforceStoreRateLimit(\n resource: string,\n priority: RequestPriority,\n signal?: AbortSignal,\n ): Promise<boolean> {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n const startedAt = Date.now();\n const hasAtomicAcquire = typeof rateLimit.acquire === 'function';\n\n const canProceedNow = async (): Promise<boolean> => {\n if (hasAtomicAcquire) {\n return rateLimit.acquire!(resource, priority);\n }\n return rateLimit.canProceed(resource, priority);\n };\n\n if (this.options.throwOnRateLimit) {\n const canProceed = await canProceedNow();\n if (!canProceed) {\n const waitTime = await rateLimit.getWaitTime(resource, priority);\n throw new Error(\n `Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`,\n );\n }\n return hasAtomicAcquire;\n }\n\n // Keep polling + waiting until the store explicitly allows the request or\n // we exhaust maxWaitTime. A single one-off sleep can otherwise let a request\n // through while still over limit.\n while (!(await canProceedNow())) {\n const suggestedWaitMs = await rateLimit.getWaitTime(resource, priority);\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for resource '${resource}'.`,\n );\n }\n\n // If a store reports \"blocked\" but no wait time, use a tiny backoff to\n // avoid a tight CPU loop while still converging quickly.\n const waitTime =\n suggestedWaitMs > 0\n ? Math.min(suggestedWaitMs, remainingWaitBudgetMs)\n : Math.min(25, remainingWaitBudgetMs);\n\n await wait(waitTime, signal);\n }\n\n return hasAtomicAcquire;\n }\n\n private generateClientError(err: unknown): Error {\n // If a custom error handler is provided, use it\n if (this.options.errorHandler) {\n return this.options.errorHandler(err);\n }\n\n if (err instanceof HttpClientError) {\n return err;\n }\n\n const responseError = err as Partial<ErrorWithResponse>;\n const statusCode =\n typeof responseError.response?.status === 'number'\n ? responseError.response.status\n : undefined;\n\n const responseData = responseError.response?.data;\n const derivedResponseMessage =\n typeof responseData === 'object' && responseData !== null\n ? (responseData as { message?: unknown }).message\n : undefined;\n const responseMessage =\n typeof derivedResponseMessage === 'string'\n ? derivedResponseMessage\n : undefined;\n\n const errorMessage =\n err instanceof Error\n ? err.message\n : typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Unknown error';\n const message = `${errorMessage}${responseMessage ? `, ${responseMessage}` : ''}`;\n\n return new HttpClientError(message, statusCode);\n }\n\n private async parseResponseBody(\n response: Response,\n ): Promise<ParsedResponseBody> {\n if (response.status === 204 || response.status === 205) {\n return { data: undefined };\n }\n\n const rawBody = await response.text();\n if (!rawBody) {\n return { data: undefined };\n }\n\n const contentType =\n response.headers.get('content-type')?.toLowerCase() ?? '';\n const shouldAttemptJsonParsing =\n contentType.includes('application/json') ||\n contentType.includes('+json') ||\n rawBody.trimStart().startsWith('{') ||\n rawBody.trimStart().startsWith('[');\n\n if (!shouldAttemptJsonParsing) {\n return { data: rawBody };\n }\n\n try {\n const parsed = JSON.parse(rawBody) as unknown;\n if (typeof parsed === 'object' && parsed !== null) {\n return { data: parsed };\n }\n\n return { data: parsed };\n } catch {\n return { data: rawBody };\n }\n }\n\n async get<Result>(\n url: string,\n options: { signal?: AbortSignal; priority?: RequestPriority } = {},\n ): Promise<Result> {\n const { signal, priority = 'background' } = options;\n const { endpoint, params } = this.parseUrlForHashing(url);\n const hash = hashRequest(endpoint, params);\n const resource = this.inferResource(url);\n\n try {\n await this.enforceServerCooldown(url, signal);\n\n // 1. Cache - check for cached response\n if (this.stores.cache) {\n const cachedResult = await this.stores.cache.get(hash);\n if (cachedResult !== undefined) {\n return cachedResult as Result;\n }\n }\n\n // 2. Deduplication - check for in-progress request\n if (this.stores.dedupe) {\n const existingResult = await this.stores.dedupe.waitFor(hash);\n if (existingResult !== undefined) {\n return existingResult as Result;\n }\n\n if (this.stores.dedupe.registerOrJoin) {\n const registration = await this.stores.dedupe.registerOrJoin(hash);\n\n if (!registration.isOwner) {\n const joinedResult = await this.stores.dedupe.waitFor(hash);\n if (joinedResult !== undefined) {\n return joinedResult as Result;\n }\n }\n } else {\n await this.stores.dedupe.register(hash);\n }\n }\n\n // 3. Rate limiting - check if request can proceed\n let alreadyRecordedRateLimit = false;\n if (this.stores.rateLimit) {\n alreadyRecordedRateLimit = await this.enforceStoreRateLimit(\n resource,\n priority,\n signal,\n );\n }\n\n // 4. Execute the actual HTTP request\n const response = await fetch(url, { signal });\n this.applyServerRateLimitHints(url, response.headers, response.status);\n\n const parsedBody = await this.parseResponseBody(response);\n\n if (!response.ok) {\n const error: ErrorWithResponse = {\n message: `Request failed with status ${response.status}`,\n response: {\n status: response.status,\n data: parsedBody.data,\n headers: response.headers,\n },\n };\n throw error;\n }\n\n // 5. Apply response transformer if provided\n let data: unknown = parsedBody.data;\n if (this.options.responseTransformer && data) {\n data = this.options.responseTransformer(data);\n }\n\n // 6. Apply response handler if provided (for domain-specific validation)\n if (this.options.responseHandler) {\n data = this.options.responseHandler(data);\n }\n\n const result = data as Result;\n\n // 7. Record the request for rate limiting\n if (this.stores.rateLimit && !alreadyRecordedRateLimit) {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n await rateLimit.record(resource, priority);\n }\n\n // 8. Cache the result\n if (this.stores.cache) {\n await this.stores.cache.set(hash, result, this.options.defaultCacheTTL);\n }\n\n // 9. Mark deduplication as complete\n if (this.stores.dedupe) {\n await this.stores.dedupe.complete(hash, result);\n }\n\n return result;\n } catch (error) {\n // Mark deduplication as failed\n if (this.stores.dedupe) {\n await this.stores.dedupe.fail(hash, error as Error);\n }\n\n // Allow callers to detect aborts distinctly – do not wrap AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n throw error;\n }\n\n // Already a processed error from the !response.ok branch above\n if (error instanceof HttpClientError) {\n throw error;\n }\n\n throw this.generateClientError(error);\n }\n }\n}\n"]}
|
package/lib/index.d.cts
CHANGED
|
@@ -140,6 +140,12 @@ type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;
|
|
|
140
140
|
* Interface for rate limiting API requests per resource
|
|
141
141
|
*/
|
|
142
142
|
interface RateLimitStore {
|
|
143
|
+
/**
|
|
144
|
+
* Atomically acquire capacity for a request if the implementation supports it.
|
|
145
|
+
* When present and returning true, callers should treat the request as already
|
|
146
|
+
* recorded for rate-limit accounting.
|
|
147
|
+
*/
|
|
148
|
+
acquire?(resource: string): Promise<boolean>;
|
|
143
149
|
/**
|
|
144
150
|
* Check if a request to a resource can proceed based on rate limits
|
|
145
151
|
* @param resource The resource name (e.g., 'issues', 'characters')
|
|
@@ -177,6 +183,10 @@ interface RateLimitStore {
|
|
|
177
183
|
* Enhanced interface for adaptive rate limiting stores with priority support
|
|
178
184
|
*/
|
|
179
185
|
interface AdaptiveRateLimitStore extends RateLimitStore {
|
|
186
|
+
/**
|
|
187
|
+
* Atomically acquire capacity for a request if the implementation supports it.
|
|
188
|
+
*/
|
|
189
|
+
acquire?(resource: string, priority?: RequestPriority): Promise<boolean>;
|
|
180
190
|
/**
|
|
181
191
|
* Check if a request to a resource can proceed based on rate limits
|
|
182
192
|
* @param resource The resource name (e.g., 'issues', 'characters')
|
|
@@ -335,7 +345,6 @@ interface HttpClientOptions {
|
|
|
335
345
|
};
|
|
336
346
|
}
|
|
337
347
|
declare class HttpClient implements HttpClientContract {
|
|
338
|
-
private _http;
|
|
339
348
|
private stores;
|
|
340
349
|
private serverCooldowns;
|
|
341
350
|
private options;
|
|
@@ -364,6 +373,7 @@ declare class HttpClient implements HttpClientContract {
|
|
|
364
373
|
private enforceServerCooldown;
|
|
365
374
|
private enforceStoreRateLimit;
|
|
366
375
|
private generateClientError;
|
|
376
|
+
private parseResponseBody;
|
|
367
377
|
get<Result>(url: string, options?: {
|
|
368
378
|
signal?: AbortSignal;
|
|
369
379
|
priority?: RequestPriority;
|
package/lib/index.d.ts
CHANGED
|
@@ -140,6 +140,12 @@ type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;
|
|
|
140
140
|
* Interface for rate limiting API requests per resource
|
|
141
141
|
*/
|
|
142
142
|
interface RateLimitStore {
|
|
143
|
+
/**
|
|
144
|
+
* Atomically acquire capacity for a request if the implementation supports it.
|
|
145
|
+
* When present and returning true, callers should treat the request as already
|
|
146
|
+
* recorded for rate-limit accounting.
|
|
147
|
+
*/
|
|
148
|
+
acquire?(resource: string): Promise<boolean>;
|
|
143
149
|
/**
|
|
144
150
|
* Check if a request to a resource can proceed based on rate limits
|
|
145
151
|
* @param resource The resource name (e.g., 'issues', 'characters')
|
|
@@ -177,6 +183,10 @@ interface RateLimitStore {
|
|
|
177
183
|
* Enhanced interface for adaptive rate limiting stores with priority support
|
|
178
184
|
*/
|
|
179
185
|
interface AdaptiveRateLimitStore extends RateLimitStore {
|
|
186
|
+
/**
|
|
187
|
+
* Atomically acquire capacity for a request if the implementation supports it.
|
|
188
|
+
*/
|
|
189
|
+
acquire?(resource: string, priority?: RequestPriority): Promise<boolean>;
|
|
180
190
|
/**
|
|
181
191
|
* Check if a request to a resource can proceed based on rate limits
|
|
182
192
|
* @param resource The resource name (e.g., 'issues', 'characters')
|
|
@@ -335,7 +345,6 @@ interface HttpClientOptions {
|
|
|
335
345
|
};
|
|
336
346
|
}
|
|
337
347
|
declare class HttpClient implements HttpClientContract {
|
|
338
|
-
private _http;
|
|
339
348
|
private stores;
|
|
340
349
|
private serverCooldowns;
|
|
341
350
|
private options;
|
|
@@ -364,6 +373,7 @@ declare class HttpClient implements HttpClientContract {
|
|
|
364
373
|
private enforceServerCooldown;
|
|
365
374
|
private enforceStoreRateLimit;
|
|
366
375
|
private generateClientError;
|
|
376
|
+
private parseResponseBody;
|
|
367
377
|
get<Result>(url: string, options?: {
|
|
368
378
|
signal?: AbortSignal;
|
|
369
379
|
priority?: RequestPriority;
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
import { createHash } from 'crypto';
|
|
4
3
|
|
|
@@ -258,7 +257,6 @@ var HttpClient = class {
|
|
|
258
257
|
constructor(stores = {}, options = {}) {
|
|
259
258
|
this.serverCooldowns = /* @__PURE__ */ new Map();
|
|
260
259
|
var _a, _b, _c;
|
|
261
|
-
this._http = axios.create();
|
|
262
260
|
this.stores = stores;
|
|
263
261
|
this.options = {
|
|
264
262
|
defaultCacheTTL: (_a = options.defaultCacheTTL) != null ? _a : 3600,
|
|
@@ -355,6 +353,15 @@ var HttpClient = class {
|
|
|
355
353
|
if (!headers) {
|
|
356
354
|
return void 0;
|
|
357
355
|
}
|
|
356
|
+
if (headers instanceof Headers) {
|
|
357
|
+
for (const rawName of names) {
|
|
358
|
+
const value = headers.get(rawName);
|
|
359
|
+
if (value !== null) {
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
358
365
|
for (const rawName of names) {
|
|
359
366
|
const name = rawName.toLowerCase();
|
|
360
367
|
const value = (_a = headers[name]) != null ? _a : headers[rawName];
|
|
@@ -481,17 +488,24 @@ var HttpClient = class {
|
|
|
481
488
|
return __async(this, null, function* () {
|
|
482
489
|
const rateLimit = this.stores.rateLimit;
|
|
483
490
|
const startedAt = Date.now();
|
|
491
|
+
const hasAtomicAcquire = typeof rateLimit.acquire === "function";
|
|
492
|
+
const canProceedNow = () => __async(this, null, function* () {
|
|
493
|
+
if (hasAtomicAcquire) {
|
|
494
|
+
return rateLimit.acquire(resource, priority);
|
|
495
|
+
}
|
|
496
|
+
return rateLimit.canProceed(resource, priority);
|
|
497
|
+
});
|
|
484
498
|
if (this.options.throwOnRateLimit) {
|
|
485
|
-
const canProceed = yield
|
|
499
|
+
const canProceed = yield canProceedNow();
|
|
486
500
|
if (!canProceed) {
|
|
487
501
|
const waitTime = yield rateLimit.getWaitTime(resource, priority);
|
|
488
502
|
throw new Error(
|
|
489
503
|
`Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`
|
|
490
504
|
);
|
|
491
505
|
}
|
|
492
|
-
return;
|
|
506
|
+
return hasAtomicAcquire;
|
|
493
507
|
}
|
|
494
|
-
while (!(yield
|
|
508
|
+
while (!(yield canProceedNow())) {
|
|
495
509
|
const suggestedWaitMs = yield rateLimit.getWaitTime(resource, priority);
|
|
496
510
|
const elapsedMs = Date.now() - startedAt;
|
|
497
511
|
const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;
|
|
@@ -503,22 +517,52 @@ var HttpClient = class {
|
|
|
503
517
|
const waitTime = suggestedWaitMs > 0 ? Math.min(suggestedWaitMs, remainingWaitBudgetMs) : Math.min(25, remainingWaitBudgetMs);
|
|
504
518
|
yield wait(waitTime, signal);
|
|
505
519
|
}
|
|
520
|
+
return hasAtomicAcquire;
|
|
506
521
|
});
|
|
507
522
|
}
|
|
508
523
|
generateClientError(err) {
|
|
509
|
-
var _a, _b
|
|
524
|
+
var _a, _b;
|
|
510
525
|
if (this.options.errorHandler) {
|
|
511
526
|
return this.options.errorHandler(err);
|
|
512
527
|
}
|
|
513
528
|
if (err instanceof HttpClientError) {
|
|
514
529
|
return err;
|
|
515
530
|
}
|
|
516
|
-
const
|
|
517
|
-
const statusCode = (_a =
|
|
518
|
-
const
|
|
519
|
-
const
|
|
531
|
+
const responseError = err;
|
|
532
|
+
const statusCode = typeof ((_a = responseError.response) == null ? void 0 : _a.status) === "number" ? responseError.response.status : void 0;
|
|
533
|
+
const responseData = (_b = responseError.response) == null ? void 0 : _b.data;
|
|
534
|
+
const derivedResponseMessage = typeof responseData === "object" && responseData !== null ? responseData.message : void 0;
|
|
535
|
+
const responseMessage = typeof derivedResponseMessage === "string" ? derivedResponseMessage : void 0;
|
|
536
|
+
const errorMessage = err instanceof Error ? err.message : typeof err.message === "string" ? err.message : "Unknown error";
|
|
537
|
+
const message = `${errorMessage}${responseMessage ? `, ${responseMessage}` : ""}`;
|
|
520
538
|
return new HttpClientError(message, statusCode);
|
|
521
539
|
}
|
|
540
|
+
parseResponseBody(response) {
|
|
541
|
+
return __async(this, null, function* () {
|
|
542
|
+
var _a, _b;
|
|
543
|
+
if (response.status === 204 || response.status === 205) {
|
|
544
|
+
return { data: void 0 };
|
|
545
|
+
}
|
|
546
|
+
const rawBody = yield response.text();
|
|
547
|
+
if (!rawBody) {
|
|
548
|
+
return { data: void 0 };
|
|
549
|
+
}
|
|
550
|
+
const contentType = (_b = (_a = response.headers.get("content-type")) == null ? void 0 : _a.toLowerCase()) != null ? _b : "";
|
|
551
|
+
const shouldAttemptJsonParsing = contentType.includes("application/json") || contentType.includes("+json") || rawBody.trimStart().startsWith("{") || rawBody.trimStart().startsWith("[");
|
|
552
|
+
if (!shouldAttemptJsonParsing) {
|
|
553
|
+
return { data: rawBody };
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const parsed = JSON.parse(rawBody);
|
|
557
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
558
|
+
return { data: parsed };
|
|
559
|
+
}
|
|
560
|
+
return { data: parsed };
|
|
561
|
+
} catch (e) {
|
|
562
|
+
return { data: rawBody };
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
522
566
|
get(_0) {
|
|
523
567
|
return __async(this, arguments, function* (url, options = {}) {
|
|
524
568
|
const { signal, priority = "background" } = options;
|
|
@@ -550,16 +594,29 @@ var HttpClient = class {
|
|
|
550
594
|
yield this.stores.dedupe.register(hash);
|
|
551
595
|
}
|
|
552
596
|
}
|
|
597
|
+
let alreadyRecordedRateLimit = false;
|
|
553
598
|
if (this.stores.rateLimit) {
|
|
554
|
-
yield this.enforceStoreRateLimit(
|
|
599
|
+
alreadyRecordedRateLimit = yield this.enforceStoreRateLimit(
|
|
600
|
+
resource,
|
|
601
|
+
priority,
|
|
602
|
+
signal
|
|
603
|
+
);
|
|
555
604
|
}
|
|
556
|
-
const response = yield
|
|
557
|
-
this.applyServerRateLimitHints(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
605
|
+
const response = yield fetch(url, { signal });
|
|
606
|
+
this.applyServerRateLimitHints(url, response.headers, response.status);
|
|
607
|
+
const parsedBody = yield this.parseResponseBody(response);
|
|
608
|
+
if (!response.ok) {
|
|
609
|
+
const error = {
|
|
610
|
+
message: `Request failed with status ${response.status}`,
|
|
611
|
+
response: {
|
|
612
|
+
status: response.status,
|
|
613
|
+
data: parsedBody.data,
|
|
614
|
+
headers: response.headers
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
let data = parsedBody.data;
|
|
563
620
|
if (this.options.responseTransformer && data) {
|
|
564
621
|
data = this.options.responseTransformer(data);
|
|
565
622
|
}
|
|
@@ -567,7 +624,7 @@ var HttpClient = class {
|
|
|
567
624
|
data = this.options.responseHandler(data);
|
|
568
625
|
}
|
|
569
626
|
const result = data;
|
|
570
|
-
if (this.stores.rateLimit) {
|
|
627
|
+
if (this.stores.rateLimit && !alreadyRecordedRateLimit) {
|
|
571
628
|
const rateLimit = this.stores.rateLimit;
|
|
572
629
|
yield rateLimit.record(resource, priority);
|
|
573
630
|
}
|
|
@@ -579,20 +636,15 @@ var HttpClient = class {
|
|
|
579
636
|
}
|
|
580
637
|
return result;
|
|
581
638
|
} catch (error) {
|
|
582
|
-
const axiosError = error;
|
|
583
|
-
if (axiosError.response) {
|
|
584
|
-
this.applyServerRateLimitHints(
|
|
585
|
-
url,
|
|
586
|
-
axiosError.response.headers,
|
|
587
|
-
axiosError.response.status
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
639
|
if (this.stores.dedupe) {
|
|
591
640
|
yield this.stores.dedupe.fail(hash, error);
|
|
592
641
|
}
|
|
593
642
|
if (error instanceof Error && error.name === "AbortError") {
|
|
594
643
|
throw error;
|
|
595
644
|
}
|
|
645
|
+
if (error instanceof HttpClientError) {
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
596
648
|
throw this.generateClientError(error);
|
|
597
649
|
}
|
|
598
650
|
});
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors/http-client-error.ts","../src/stores/rate-limit-store.ts","../src/stores/request-hasher.ts","../src/stores/rate-limit-config.ts","../src/stores/adaptive-capacity-calculator.ts","../src/http-client/http-client.ts"],"names":["baseUserCapacity"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base error class for HTTP client errors.\n * Consumers can extend this for domain-specific error handling.\n */\nexport class HttpClientError extends Error {\n public readonly statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = 'HttpClientError';\n this.statusCode = statusCode;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { z } from 'zod';\n\n/**\n * Priority level for API requests\n */\nexport type RequestPriority = 'user' | 'background';\n\n/**\n * Adaptive configuration schema with validation and defaults\n */\nexport const AdaptiveConfigSchema = z\n .object({\n monitoringWindowMs: z\n .number()\n .positive()\n .default(15 * 60 * 1000), // 15 minutes\n highActivityThreshold: z.number().min(0).default(10), // requests per window\n moderateActivityThreshold: z.number().min(0).default(3),\n recalculationIntervalMs: z.number().positive().default(30000), // 30 seconds\n sustainedInactivityThresholdMs: z\n .number()\n .positive()\n .default(30 * 60 * 1000), // 30 minutes\n backgroundPauseOnIncreasingTrend: z.boolean().default(true),\n maxUserScaling: z.number().positive().default(2.0), // don't exceed 2x capacity\n minUserReserved: z.number().min(0).default(5), // requests minimum\n })\n .refine(\n (data) => {\n return data.moderateActivityThreshold < data.highActivityThreshold;\n },\n {\n message:\n 'moderateActivityThreshold must be less than highActivityThreshold',\n },\n );\n\n/**\n * Configuration for adaptive rate limiting\n */\nexport type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;\n\n/**\n * Interface for rate limiting API requests per resource\n */\nexport interface RateLimitStore {\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n */\n record(resource: string): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n }>;\n\n /**\n * Reset rate limits for a resource (useful for testing)\n * @param resource The resource name\n */\n reset(resource: string): Promise<void>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string): Promise<number>;\n}\n\n/**\n * Enhanced interface for adaptive rate limiting stores with priority support\n */\nexport interface AdaptiveRateLimitStore extends RateLimitStore {\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string, priority?: RequestPriority): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n */\n record(resource: string, priority?: RequestPriority): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n adaptive?: {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n recentUserActivity: number;\n reason: string;\n };\n }>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @param priority The priority level of the request (defaults to 'background')\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string, priority?: RequestPriority): Promise<number>;\n}\n","import { createHash } from 'crypto';\n\n/**\n * Creates a consistent hash for API requests to use as cache/dedupe keys\n * @param endpoint The API endpoint\n * @param params The request parameters\n * @returns A SHA-256 hash of the request\n */\nexport function hashRequest(\n endpoint: string,\n params: Record<string, unknown> = {},\n): string {\n const requestString = JSON.stringify({\n endpoint,\n params: sortObject(params),\n });\n\n return createHash('sha256').update(requestString).digest('hex');\n}\n\n/**\n * Normalises and sorts an object for hashing purposes.\n *\n * The ComicVine API transmits all query parameters as strings. To avoid cache\n * misses caused by treating `10` and `'10'` as different values we normalise\n * primitive types (number and boolean) to their string representation **before**\n * sorting. `undefined` values are intentionally kept as `undefined` so that\n * they are dropped by `JSON.stringify`, maintaining the existing behaviour\n * where an omitted parameter and an `undefined` parameter produce the same\n * hash.\n */\nfunction sortObject(obj: unknown): unknown {\n // Handle primitives first\n if (obj === null) {\n return null;\n }\n\n const objType = typeof obj;\n\n if (objType === 'undefined' || objType === 'string') {\n return obj;\n }\n\n if (objType === 'number' || objType === 'boolean') {\n // Convert to string so that 10 and '10' (or true and 'true') hash equally\n return String(obj);\n }\n\n // Recursively process arrays\n if (Array.isArray(obj)) {\n return obj.map(sortObject);\n }\n\n // For objects – sort keys and recurse\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj as Record<string, unknown>).sort();\n\n for (const key of keys) {\n const value = (obj as Record<string, unknown>)[key];\n const normalisedValue = sortObject(value);\n\n // Skip keys whose value normalises to undefined so omitted & undefined match\n if (normalisedValue !== undefined) {\n sorted[key] = normalisedValue;\n }\n }\n\n return sorted;\n}\n","/**\n * Configuration for per-resource rate limiting.\n *\n * This interface is shared by all store implementations (e.g. in-memory,\n * SQLite) so that callers can use a single canonical type.\n */\nexport interface RateLimitConfig {\n /** Number of requests allowed per time window */\n limit: number;\n /** Duration of the window in milliseconds */\n windowMs: number;\n}\n\n/**\n * Default rate-limit window: 60 requests per minute.\n *\n * Store implementations can reference this to avoid duplicating magic numbers.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimitConfig = {\n limit: 60,\n windowMs: 60_000,\n};\n","import { z } from 'zod';\nimport { AdaptiveConfigSchema } from './rate-limit-store.js';\n\ninterface ActivityMetrics {\n recentUserRequests: Array<number>;\n recentBackgroundRequests: Array<number>;\n userActivityTrend: 'increasing' | 'stable' | 'decreasing' | 'none';\n}\n\ninterface DynamicCapacityResult {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n reason: string;\n}\n\n/**\n * Calculates dynamic capacity allocation based on real-time user activity patterns\n */\nexport class AdaptiveCapacityCalculator {\n public readonly config: z.infer<typeof AdaptiveConfigSchema>;\n\n constructor(config: Partial<z.input<typeof AdaptiveConfigSchema>> = {}) {\n // Zod handles validation and applies defaults automatically\n this.config = AdaptiveConfigSchema.parse(config);\n }\n\n calculateDynamicCapacity(\n resource: string,\n totalLimit: number,\n activityMetrics: ActivityMetrics,\n ): DynamicCapacityResult {\n const recentUserActivity = this.getRecentActivity(\n activityMetrics.recentUserRequests,\n );\n const activityTrend = this.calculateActivityTrend(\n activityMetrics.recentUserRequests,\n );\n\n // Strategy 1: High Activity - Pause Background\n if (recentUserActivity >= this.config.highActivityThreshold) {\n const userCapacity = Math.min(\n totalLimit * 0.9,\n Math.floor(totalLimit * 0.5 * this.config.maxUserScaling), // 50% base * scaling factor\n );\n\n return {\n userReserved: userCapacity,\n backgroundMax: totalLimit - userCapacity,\n backgroundPaused:\n this.config.backgroundPauseOnIncreasingTrend &&\n activityTrend === 'increasing',\n reason: `High user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - prioritizing users`,\n };\n }\n\n // Strategy 2: Moderate Activity - Balanced Scaling\n if (recentUserActivity >= this.config.moderateActivityThreshold) {\n const userMultiplier = this.getUserMultiplier(\n recentUserActivity,\n activityTrend,\n );\n const baseUserCapacity = Math.floor(totalLimit * 0.4); // 40% base allocation\n const dynamicUserCapacity = Math.min(\n totalLimit * 0.7,\n baseUserCapacity * userMultiplier,\n );\n\n return {\n userReserved: dynamicUserCapacity,\n backgroundMax: totalLimit - dynamicUserCapacity,\n backgroundPaused: false,\n reason: `Moderate user activity - dynamic scaling (${userMultiplier.toFixed(1)}x user capacity)`,\n };\n }\n\n // Strategy 3: Low/No Activity - Background Scale Up\n if (recentUserActivity === 0) {\n // If there have never been any requests at all (fresh start), use default capacity allocation\n if (\n activityMetrics.recentUserRequests.length === 0 &&\n activityMetrics.recentBackgroundRequests.length === 0\n ) {\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for initial state\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit -\n Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: 'Initial state - default capacity allocation',\n };\n }\n\n // If there have never been user requests (only background), use background scale up\n if (activityMetrics.recentUserRequests.length === 0) {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'No user activity yet - background scale up with minimal user buffer',\n };\n }\n\n // There have been user requests before, check for sustained inactivity\n const sustainedInactivity = this.getSustainedInactivityPeriod(\n activityMetrics.recentUserRequests,\n );\n\n if (sustainedInactivity > this.config.sustainedInactivityThresholdMs) {\n return {\n userReserved: 0, // No reservation - background gets everything!\n backgroundMax: totalLimit, // Full capacity available\n backgroundPaused: false,\n reason: `Sustained zero activity (${Math.floor(sustainedInactivity / 60000)}+ min) - full capacity to background`,\n };\n } else {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'Recent zero activity - background scale up with minimal user buffer',\n };\n }\n }\n\n // Strategy 4: Very Low Activity - Gradual Background Scale Up\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for very low activity\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit - Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: `Low user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - background scale up`,\n };\n }\n\n getRecentActivity(requests: Array<number>): number {\n const cutoff = Date.now() - this.config.monitoringWindowMs;\n return requests.filter((timestamp) => timestamp > cutoff).length;\n }\n\n calculateActivityTrend(\n requests: Array<number>,\n ): 'increasing' | 'stable' | 'decreasing' | 'none' {\n const now = Date.now();\n const windowSize = this.config.monitoringWindowMs / 3; // Use 1/3 of monitoring window for trend\n const recent = requests.filter((t) => t > now - windowSize).length;\n const previous = requests.filter(\n (t) => t > now - 2 * windowSize && t <= now - windowSize,\n ).length;\n\n if (recent === 0 && previous === 0) return 'none';\n if (recent > previous * 1.5) return 'increasing';\n if (recent < previous * 0.5) return 'decreasing';\n return 'stable';\n }\n\n private getUserMultiplier(activity: number, trend: string): number {\n let base = Math.min(\n this.config.maxUserScaling,\n 1 + activity / this.config.highActivityThreshold,\n );\n\n // Adjust based on trend\n if (trend === 'increasing') base *= 1.2;\n if (trend === 'decreasing') base *= 0.8;\n\n return Math.max(1.0, base);\n }\n\n private getSustainedInactivityPeriod(requests: Array<number>): number {\n if (requests.length === 0) {\n // This should not be called when there are no requests (handled above)\n return 0;\n }\n\n const lastRequest = Math.max(...requests);\n return Date.now() - lastRequest;\n }\n}\n\n// Export types for use in other modules\nexport type { ActivityMetrics, DynamicCapacityResult };\n","import axios, { AxiosError } from 'axios';\nimport { HttpClientError } from '../errors/http-client-error.js';\nimport {\n CacheStore,\n DedupeStore,\n RateLimitStore,\n AdaptiveRateLimitStore,\n RequestPriority,\n hashRequest,\n} from '../stores/index.js';\nimport { HttpClientContract } from '../types/index.js';\n\nconst DEFAULT_RATE_LIMIT_HEADER_NAMES = {\n retryAfter: ['retry-after'],\n limit: ['ratelimit-limit', 'x-ratelimit-limit', 'rate-limit-limit'],\n remaining: [\n 'ratelimit-remaining',\n 'x-ratelimit-remaining',\n 'rate-limit-remaining',\n ],\n reset: ['ratelimit-reset', 'x-ratelimit-reset', 'rate-limit-reset'],\n combined: ['ratelimit'],\n} as const;\n\n/**\n * Wait for a specified period while supporting cancellation via AbortSignal.\n *\n * If the signal is aborted before the timeout completes the promise rejects\n * with an `Error` whose name is set to `AbortError`, mimicking DOMException in\n * browser environments without depending on it. This allows callers to use a\n * single `AbortController` for both the rate-limit wait *and* the subsequent\n * HTTP request.\n */\nfunction wait(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n if (signal) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n function onAbort() {\n clearTimeout(timer);\n const err = new Error('Aborted');\n err.name = 'AbortError';\n reject(err);\n }\n\n if (signal) {\n if (signal.aborted) {\n onAbort();\n } else {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n });\n}\n\nexport interface HttpClientStores {\n cache?: CacheStore;\n dedupe?: DedupeStore;\n rateLimit?: RateLimitStore | AdaptiveRateLimitStore;\n}\n\nexport interface HttpClientOptions {\n /**\n * Default cache TTL in seconds\n */\n defaultCacheTTL?: number;\n /**\n * Whether to throw errors on rate limit violations\n */\n throwOnRateLimit?: boolean;\n /**\n * Maximum time to wait for rate limit in milliseconds\n */\n maxWaitTime?: number;\n /**\n * Optional response transformer applied to the raw response data.\n * Use this for converting snake_case to camelCase, etc.\n */\n responseTransformer?: (data: unknown) => unknown;\n /**\n * Optional error handler to convert errors into domain-specific error types.\n * If not provided, a generic HttpClientError is thrown.\n */\n errorHandler?: (error: unknown) => Error;\n /**\n * Optional response validator/handler called after transformation.\n * Use this to inspect the response and throw domain-specific errors\n * based on response content (e.g., API-level error codes).\n */\n responseHandler?: (data: unknown) => unknown;\n /**\n * Configure rate-limit response header names for standards and custom APIs.\n */\n rateLimitHeaders?: {\n retryAfter?: Array<string>;\n limit?: Array<string>;\n remaining?: Array<string>;\n reset?: Array<string>;\n combined?: Array<string>;\n };\n}\n\ninterface RateLimitHeaderConfig {\n retryAfter: Array<string>;\n limit: Array<string>;\n remaining: Array<string>;\n reset: Array<string>;\n combined: Array<string>;\n}\n\nexport class HttpClient implements HttpClientContract {\n private _http;\n private stores: HttpClientStores;\n private serverCooldowns = new Map<string, number>();\n private options: Required<\n Pick<\n HttpClientOptions,\n 'defaultCacheTTL' | 'throwOnRateLimit' | 'maxWaitTime'\n >\n > &\n Pick<\n HttpClientOptions,\n 'responseTransformer' | 'errorHandler' | 'responseHandler'\n > & {\n rateLimitHeaders: RateLimitHeaderConfig;\n };\n\n constructor(stores: HttpClientStores = {}, options: HttpClientOptions = {}) {\n this._http = axios.create();\n this.stores = stores;\n this.options = {\n defaultCacheTTL: options.defaultCacheTTL ?? 3600,\n throwOnRateLimit: options.throwOnRateLimit ?? true,\n maxWaitTime: options.maxWaitTime ?? 60000,\n responseTransformer: options.responseTransformer,\n errorHandler: options.errorHandler,\n responseHandler: options.responseHandler,\n rateLimitHeaders: this.normalizeRateLimitHeaders(\n options.rateLimitHeaders,\n ),\n };\n }\n\n private normalizeRateLimitHeaders(\n customHeaders?: HttpClientOptions['rateLimitHeaders'],\n ): RateLimitHeaderConfig {\n return {\n retryAfter: this.normalizeHeaderNames(\n customHeaders?.retryAfter,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.retryAfter,\n ),\n limit: this.normalizeHeaderNames(\n customHeaders?.limit,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.limit,\n ),\n remaining: this.normalizeHeaderNames(\n customHeaders?.remaining,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.remaining,\n ),\n reset: this.normalizeHeaderNames(\n customHeaders?.reset,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.reset,\n ),\n combined: this.normalizeHeaderNames(\n customHeaders?.combined,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.combined,\n ),\n };\n }\n\n private normalizeHeaderNames(\n providedNames: Array<string> | undefined,\n defaultNames: ReadonlyArray<string>,\n ): Array<string> {\n if (!providedNames || providedNames.length === 0) {\n return [...defaultNames];\n }\n\n const customNames = providedNames\n .map((name) => name.trim().toLowerCase())\n .filter(Boolean);\n\n if (customNames.length === 0) {\n return [...defaultNames];\n }\n\n return [...new Set([...customNames, ...defaultNames])];\n }\n\n /**\n * Infer the resource name from the endpoint URL\n * @param url The full URL or endpoint path\n * @returns The resource name for rate limiting\n */\n private inferResource(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use the first meaningful path segment as the resource name\n const segments = urlObj.pathname.split('/').filter(Boolean);\n return segments[segments.length - 1] || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Extract endpoint and params from URL for request hashing\n * @param url The full URL\n * @returns Object with endpoint and params for hashing\n */\n private parseUrlForHashing(url: string): {\n endpoint: string;\n params: Record<string, unknown>;\n } {\n const urlObj = new URL(url);\n const endpoint = `${urlObj.origin}${urlObj.pathname}`;\n const params: Record<string, unknown> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n const existing = params[key];\n\n // Keep repeated query keys as arrays so semantically distinct URLs like\n // `?tag=a&tag=b` and `?tag=b` do not hash to the same cache/dedupe key.\n if (existing === undefined) {\n params[key] = value;\n return;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n return;\n }\n\n params[key] = [existing, value];\n });\n\n return { endpoint, params };\n }\n\n private getOriginScope(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return 'unknown';\n }\n }\n\n private getHeaderValue(\n headers: Record<string, unknown> | undefined,\n names: Array<string>,\n ): string | undefined {\n if (!headers) {\n return undefined;\n }\n\n for (const rawName of names) {\n const name = rawName.toLowerCase();\n const value = headers[name] ?? headers[rawName];\n\n if (typeof value === 'string') {\n return value;\n }\n\n if (Array.isArray(value) && value.length > 0) {\n const first = value.find((entry) => typeof entry === 'string');\n if (typeof first === 'string') {\n return first;\n }\n }\n }\n\n return undefined;\n }\n\n private parseIntegerHeader(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const parsed = Number.parseInt(value.trim(), 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return undefined;\n }\n\n return parsed;\n }\n\n private parseRetryAfterMs(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const numeric = Number.parseInt(value.trim(), 10);\n if (Number.isFinite(numeric) && numeric >= 0) {\n return numeric * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (!Number.isFinite(dateMs)) {\n return undefined;\n }\n\n return Math.max(0, dateMs - Date.now());\n }\n\n private parseResetMs(value: string | undefined): number | undefined {\n const parsed = this.parseIntegerHeader(value);\n if (parsed === undefined) {\n return undefined;\n }\n\n if (parsed === 0) {\n return 0;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n\n if (parsed > nowSeconds + 1) {\n return Math.max(0, (parsed - nowSeconds) * 1000);\n }\n\n return parsed * 1000;\n }\n\n private parseCombinedRateLimitHeader(value: string | undefined): {\n remaining?: number;\n resetMs?: number;\n } {\n if (!value) {\n return {};\n }\n\n const remainingMatch = value.match(/(?:^|[;,])\\s*r\\s*=\\s*(\\d+)/i);\n const resetMatch = value.match(/(?:^|[;,])\\s*t\\s*=\\s*(\\d+)/i);\n\n return {\n remaining: remainingMatch\n ? this.parseIntegerHeader(remainingMatch[1])\n : undefined,\n resetMs: resetMatch ? this.parseResetMs(resetMatch[1]) : undefined,\n };\n }\n\n private applyServerRateLimitHints(\n url: string,\n headers: Record<string, unknown> | undefined,\n statusCode?: number,\n ): void {\n if (!headers) {\n return;\n }\n\n const config = this.options.rateLimitHeaders;\n const retryAfterRaw = this.getHeaderValue(headers, config.retryAfter);\n const resetRaw = this.getHeaderValue(headers, config.reset);\n const remainingRaw = this.getHeaderValue(headers, config.remaining);\n const combinedRaw = this.getHeaderValue(headers, config.combined);\n\n const retryAfterMs = this.parseRetryAfterMs(retryAfterRaw);\n const resetMs = this.parseResetMs(resetRaw);\n const remaining = this.parseIntegerHeader(remainingRaw);\n const combined = this.parseCombinedRateLimitHeader(combinedRaw);\n\n const effectiveRemaining = remaining ?? combined.remaining;\n const effectiveResetMs = resetMs ?? combined.resetMs;\n const hasRateLimitErrorStatus = statusCode === 429 || statusCode === 503;\n\n let waitMs: number | undefined;\n\n if (retryAfterMs !== undefined) {\n waitMs = retryAfterMs;\n } else if (\n effectiveResetMs !== undefined &&\n (hasRateLimitErrorStatus ||\n (effectiveRemaining !== undefined && effectiveRemaining <= 0))\n ) {\n waitMs = effectiveResetMs;\n }\n\n if (waitMs === undefined || waitMs <= 0) {\n return;\n }\n\n const scope = this.getOriginScope(url);\n this.serverCooldowns.set(scope, Date.now() + waitMs);\n }\n\n private async enforceServerCooldown(\n url: string,\n signal?: AbortSignal,\n ): Promise<void> {\n const scope = this.getOriginScope(url);\n const startedAt = Date.now();\n\n // Re-check cooldown after each sleep so we never proceed while a server\n // cooldown is still active. This avoids bypassing limits when cooldown\n // duration is longer than maxWaitTime.\n while (true) {\n const cooldownUntil = this.serverCooldowns.get(scope);\n if (!cooldownUntil) {\n return;\n }\n\n const waitMs = cooldownUntil - Date.now();\n if (waitMs <= 0) {\n this.serverCooldowns.delete(scope);\n return;\n }\n\n if (this.options.throwOnRateLimit) {\n throw new Error(\n `Rate limit exceeded for origin '${scope}'. Wait ${waitMs}ms before retrying.`,\n );\n }\n\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for origin '${scope}'.`,\n );\n }\n\n await wait(Math.min(waitMs, remainingWaitBudgetMs), signal);\n }\n }\n\n private async enforceStoreRateLimit(\n resource: string,\n priority: RequestPriority,\n signal?: AbortSignal,\n ): Promise<void> {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n const startedAt = Date.now();\n\n if (this.options.throwOnRateLimit) {\n const canProceed = await rateLimit.canProceed(resource, priority);\n if (!canProceed) {\n const waitTime = await rateLimit.getWaitTime(resource, priority);\n throw new Error(\n `Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`,\n );\n }\n return;\n }\n\n // Keep polling + waiting until the store explicitly allows the request or\n // we exhaust maxWaitTime. A single one-off sleep can otherwise let a request\n // through while still over limit.\n while (!(await rateLimit.canProceed(resource, priority))) {\n const suggestedWaitMs = await rateLimit.getWaitTime(resource, priority);\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for resource '${resource}'.`,\n );\n }\n\n // If a store reports \"blocked\" but no wait time, use a tiny backoff to\n // avoid a tight CPU loop while still converging quickly.\n const waitTime =\n suggestedWaitMs > 0\n ? Math.min(suggestedWaitMs, remainingWaitBudgetMs)\n : Math.min(25, remainingWaitBudgetMs);\n\n await wait(waitTime, signal);\n }\n }\n\n private generateClientError(err: unknown): Error {\n // If a custom error handler is provided, use it\n if (this.options.errorHandler) {\n return this.options.errorHandler(err);\n }\n\n if (err instanceof HttpClientError) {\n return err;\n }\n\n const error = err as AxiosError<{ message?: string }>;\n const statusCode = error.response?.status;\n const errorMessage = error.response?.data?.message;\n const message = `${error.message}${errorMessage ? `, ${errorMessage}` : ''}`;\n\n return new HttpClientError(message, statusCode);\n }\n\n async get<Result>(\n url: string,\n options: { signal?: AbortSignal; priority?: RequestPriority } = {},\n ): Promise<Result> {\n const { signal, priority = 'background' } = options;\n const { endpoint, params } = this.parseUrlForHashing(url);\n const hash = hashRequest(endpoint, params);\n const resource = this.inferResource(url);\n\n try {\n await this.enforceServerCooldown(url, signal);\n\n // 1. Cache - check for cached response\n if (this.stores.cache) {\n const cachedResult = await this.stores.cache.get(hash);\n if (cachedResult !== undefined) {\n return cachedResult as Result;\n }\n }\n\n // 2. Deduplication - check for in-progress request\n if (this.stores.dedupe) {\n const existingResult = await this.stores.dedupe.waitFor(hash);\n if (existingResult !== undefined) {\n return existingResult as Result;\n }\n\n if (this.stores.dedupe.registerOrJoin) {\n const registration = await this.stores.dedupe.registerOrJoin(hash);\n\n if (!registration.isOwner) {\n const joinedResult = await this.stores.dedupe.waitFor(hash);\n if (joinedResult !== undefined) {\n return joinedResult as Result;\n }\n }\n } else {\n await this.stores.dedupe.register(hash);\n }\n }\n\n // 3. Rate limiting - check if request can proceed\n if (this.stores.rateLimit) {\n await this.enforceStoreRateLimit(resource, priority, signal);\n }\n\n // 4. Execute the actual HTTP request\n const response = await this._http.get(url, { signal });\n this.applyServerRateLimitHints(\n url,\n response.headers as Record<string, unknown>,\n response.status,\n );\n\n // 5. Apply response transformer if provided\n let data = response.data;\n if (this.options.responseTransformer && data) {\n data = this.options.responseTransformer(data);\n }\n\n // 6. Apply response handler if provided (for domain-specific validation)\n if (this.options.responseHandler) {\n data = this.options.responseHandler(data);\n }\n\n const result = data as Result;\n\n // 7. Record the request for rate limiting\n if (this.stores.rateLimit) {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n await rateLimit.record(resource, priority);\n }\n\n // 8. Cache the result\n if (this.stores.cache) {\n await this.stores.cache.set(hash, result, this.options.defaultCacheTTL);\n }\n\n // 9. Mark deduplication as complete\n if (this.stores.dedupe) {\n await this.stores.dedupe.complete(hash, result);\n }\n\n return result;\n } catch (error) {\n const axiosError = error as AxiosError;\n if (axiosError.response) {\n this.applyServerRateLimitHints(\n url,\n axiosError.response.headers as Record<string, unknown>,\n axiosError.response.status,\n );\n }\n\n // Mark deduplication as failed\n if (this.stores.dedupe) {\n await this.stores.dedupe.fail(hash, error as Error);\n }\n\n // Allow callers to detect aborts distinctly – do not wrap AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n throw error;\n }\n\n throw this.generateClientError(error);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors/http-client-error.ts","../src/stores/rate-limit-store.ts","../src/stores/request-hasher.ts","../src/stores/rate-limit-config.ts","../src/stores/adaptive-capacity-calculator.ts","../src/http-client/http-client.ts"],"names":["baseUserCapacity"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base error class for HTTP client errors.\n * Consumers can extend this for domain-specific error handling.\n */\nexport class HttpClientError extends Error {\n public readonly statusCode?: number;\n\n constructor(message: string, statusCode?: number) {\n super(message);\n this.name = 'HttpClientError';\n this.statusCode = statusCode;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { z } from 'zod';\n\n/**\n * Priority level for API requests\n */\nexport type RequestPriority = 'user' | 'background';\n\n/**\n * Adaptive configuration schema with validation and defaults\n */\nexport const AdaptiveConfigSchema = z\n .object({\n monitoringWindowMs: z\n .number()\n .positive()\n .default(15 * 60 * 1000), // 15 minutes\n highActivityThreshold: z.number().min(0).default(10), // requests per window\n moderateActivityThreshold: z.number().min(0).default(3),\n recalculationIntervalMs: z.number().positive().default(30000), // 30 seconds\n sustainedInactivityThresholdMs: z\n .number()\n .positive()\n .default(30 * 60 * 1000), // 30 minutes\n backgroundPauseOnIncreasingTrend: z.boolean().default(true),\n maxUserScaling: z.number().positive().default(2.0), // don't exceed 2x capacity\n minUserReserved: z.number().min(0).default(5), // requests minimum\n })\n .refine(\n (data) => {\n return data.moderateActivityThreshold < data.highActivityThreshold;\n },\n {\n message:\n 'moderateActivityThreshold must be less than highActivityThreshold',\n },\n );\n\n/**\n * Configuration for adaptive rate limiting\n */\nexport type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;\n\n/**\n * Interface for rate limiting API requests per resource\n */\nexport interface RateLimitStore {\n /**\n * Atomically acquire capacity for a request if the implementation supports it.\n * When present and returning true, callers should treat the request as already\n * recorded for rate-limit accounting.\n */\n acquire?(resource: string): Promise<boolean>;\n\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n */\n record(resource: string): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n }>;\n\n /**\n * Reset rate limits for a resource (useful for testing)\n * @param resource The resource name\n */\n reset(resource: string): Promise<void>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string): Promise<number>;\n}\n\n/**\n * Enhanced interface for adaptive rate limiting stores with priority support\n */\nexport interface AdaptiveRateLimitStore extends RateLimitStore {\n /**\n * Atomically acquire capacity for a request if the implementation supports it.\n */\n acquire?(resource: string, priority?: RequestPriority): Promise<boolean>;\n\n /**\n * Check if a request to a resource can proceed based on rate limits\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n * @returns True if the request can proceed, false if rate limited\n */\n canProceed(resource: string, priority?: RequestPriority): Promise<boolean>;\n\n /**\n * Record a request to a resource for rate limiting tracking\n * @param resource The resource name (e.g., 'issues', 'characters')\n * @param priority The priority level of the request (defaults to 'background')\n */\n record(resource: string, priority?: RequestPriority): Promise<void>;\n\n /**\n * Get the current rate limit status for a resource\n * @param resource The resource name\n * @returns Rate limit information including remaining requests and reset time\n */\n getStatus(resource: string): Promise<{\n remaining: number;\n resetTime: Date;\n limit: number;\n adaptive?: {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n recentUserActivity: number;\n reason: string;\n };\n }>;\n\n /**\n * Get the time in milliseconds until the next request can be made\n * @param resource The resource name\n * @param priority The priority level of the request (defaults to 'background')\n * @returns Milliseconds to wait, or 0 if no waiting is needed\n */\n getWaitTime(resource: string, priority?: RequestPriority): Promise<number>;\n}\n","import { createHash } from 'crypto';\n\n/**\n * Creates a consistent hash for API requests to use as cache/dedupe keys\n * @param endpoint The API endpoint\n * @param params The request parameters\n * @returns A SHA-256 hash of the request\n */\nexport function hashRequest(\n endpoint: string,\n params: Record<string, unknown> = {},\n): string {\n const requestString = JSON.stringify({\n endpoint,\n params: sortObject(params),\n });\n\n return createHash('sha256').update(requestString).digest('hex');\n}\n\n/**\n * Normalises and sorts an object for hashing purposes.\n *\n * The ComicVine API transmits all query parameters as strings. To avoid cache\n * misses caused by treating `10` and `'10'` as different values we normalise\n * primitive types (number and boolean) to their string representation **before**\n * sorting. `undefined` values are intentionally kept as `undefined` so that\n * they are dropped by `JSON.stringify`, maintaining the existing behaviour\n * where an omitted parameter and an `undefined` parameter produce the same\n * hash.\n */\nfunction sortObject(obj: unknown): unknown {\n // Handle primitives first\n if (obj === null) {\n return null;\n }\n\n const objType = typeof obj;\n\n if (objType === 'undefined' || objType === 'string') {\n return obj;\n }\n\n if (objType === 'number' || objType === 'boolean') {\n // Convert to string so that 10 and '10' (or true and 'true') hash equally\n return String(obj);\n }\n\n // Recursively process arrays\n if (Array.isArray(obj)) {\n return obj.map(sortObject);\n }\n\n // For objects – sort keys and recurse\n const sorted: Record<string, unknown> = {};\n const keys = Object.keys(obj as Record<string, unknown>).sort();\n\n for (const key of keys) {\n const value = (obj as Record<string, unknown>)[key];\n const normalisedValue = sortObject(value);\n\n // Skip keys whose value normalises to undefined so omitted & undefined match\n if (normalisedValue !== undefined) {\n sorted[key] = normalisedValue;\n }\n }\n\n return sorted;\n}\n","/**\n * Configuration for per-resource rate limiting.\n *\n * This interface is shared by all store implementations (e.g. in-memory,\n * SQLite) so that callers can use a single canonical type.\n */\nexport interface RateLimitConfig {\n /** Number of requests allowed per time window */\n limit: number;\n /** Duration of the window in milliseconds */\n windowMs: number;\n}\n\n/**\n * Default rate-limit window: 60 requests per minute.\n *\n * Store implementations can reference this to avoid duplicating magic numbers.\n */\nexport const DEFAULT_RATE_LIMIT: RateLimitConfig = {\n limit: 60,\n windowMs: 60_000,\n};\n","import { z } from 'zod';\nimport { AdaptiveConfigSchema } from './rate-limit-store.js';\n\ninterface ActivityMetrics {\n recentUserRequests: Array<number>;\n recentBackgroundRequests: Array<number>;\n userActivityTrend: 'increasing' | 'stable' | 'decreasing' | 'none';\n}\n\ninterface DynamicCapacityResult {\n userReserved: number;\n backgroundMax: number;\n backgroundPaused: boolean;\n reason: string;\n}\n\n/**\n * Calculates dynamic capacity allocation based on real-time user activity patterns\n */\nexport class AdaptiveCapacityCalculator {\n public readonly config: z.infer<typeof AdaptiveConfigSchema>;\n\n constructor(config: Partial<z.input<typeof AdaptiveConfigSchema>> = {}) {\n // Zod handles validation and applies defaults automatically\n this.config = AdaptiveConfigSchema.parse(config);\n }\n\n calculateDynamicCapacity(\n resource: string,\n totalLimit: number,\n activityMetrics: ActivityMetrics,\n ): DynamicCapacityResult {\n const recentUserActivity = this.getRecentActivity(\n activityMetrics.recentUserRequests,\n );\n const activityTrend = this.calculateActivityTrend(\n activityMetrics.recentUserRequests,\n );\n\n // Strategy 1: High Activity - Pause Background\n if (recentUserActivity >= this.config.highActivityThreshold) {\n const userCapacity = Math.min(\n totalLimit * 0.9,\n Math.floor(totalLimit * 0.5 * this.config.maxUserScaling), // 50% base * scaling factor\n );\n\n return {\n userReserved: userCapacity,\n backgroundMax: totalLimit - userCapacity,\n backgroundPaused:\n this.config.backgroundPauseOnIncreasingTrend &&\n activityTrend === 'increasing',\n reason: `High user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - prioritizing users`,\n };\n }\n\n // Strategy 2: Moderate Activity - Balanced Scaling\n if (recentUserActivity >= this.config.moderateActivityThreshold) {\n const userMultiplier = this.getUserMultiplier(\n recentUserActivity,\n activityTrend,\n );\n const baseUserCapacity = Math.floor(totalLimit * 0.4); // 40% base allocation\n const dynamicUserCapacity = Math.min(\n totalLimit * 0.7,\n baseUserCapacity * userMultiplier,\n );\n\n return {\n userReserved: dynamicUserCapacity,\n backgroundMax: totalLimit - dynamicUserCapacity,\n backgroundPaused: false,\n reason: `Moderate user activity - dynamic scaling (${userMultiplier.toFixed(1)}x user capacity)`,\n };\n }\n\n // Strategy 3: Low/No Activity - Background Scale Up\n if (recentUserActivity === 0) {\n // If there have never been any requests at all (fresh start), use default capacity allocation\n if (\n activityMetrics.recentUserRequests.length === 0 &&\n activityMetrics.recentBackgroundRequests.length === 0\n ) {\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for initial state\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit -\n Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: 'Initial state - default capacity allocation',\n };\n }\n\n // If there have never been user requests (only background), use background scale up\n if (activityMetrics.recentUserRequests.length === 0) {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'No user activity yet - background scale up with minimal user buffer',\n };\n }\n\n // There have been user requests before, check for sustained inactivity\n const sustainedInactivity = this.getSustainedInactivityPeriod(\n activityMetrics.recentUserRequests,\n );\n\n if (sustainedInactivity > this.config.sustainedInactivityThresholdMs) {\n return {\n userReserved: 0, // No reservation - background gets everything!\n backgroundMax: totalLimit, // Full capacity available\n backgroundPaused: false,\n reason: `Sustained zero activity (${Math.floor(sustainedInactivity / 60000)}+ min) - full capacity to background`,\n };\n } else {\n return {\n userReserved: this.config.minUserReserved, // Minimal safety buffer\n backgroundMax: totalLimit - this.config.minUserReserved,\n backgroundPaused: false,\n reason:\n 'Recent zero activity - background scale up with minimal user buffer',\n };\n }\n }\n\n // Strategy 4: Very Low Activity - Gradual Background Scale Up\n const baseUserCapacity = Math.floor(totalLimit * 0.3); // 30% base for very low activity\n return {\n userReserved: Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundMax:\n totalLimit - Math.max(baseUserCapacity, this.config.minUserReserved),\n backgroundPaused: false,\n reason: `Low user activity (${recentUserActivity} requests/${this.config.monitoringWindowMs / 60000}min) - background scale up`,\n };\n }\n\n getRecentActivity(requests: Array<number>): number {\n const cutoff = Date.now() - this.config.monitoringWindowMs;\n return requests.filter((timestamp) => timestamp > cutoff).length;\n }\n\n calculateActivityTrend(\n requests: Array<number>,\n ): 'increasing' | 'stable' | 'decreasing' | 'none' {\n const now = Date.now();\n const windowSize = this.config.monitoringWindowMs / 3; // Use 1/3 of monitoring window for trend\n const recent = requests.filter((t) => t > now - windowSize).length;\n const previous = requests.filter(\n (t) => t > now - 2 * windowSize && t <= now - windowSize,\n ).length;\n\n if (recent === 0 && previous === 0) return 'none';\n if (recent > previous * 1.5) return 'increasing';\n if (recent < previous * 0.5) return 'decreasing';\n return 'stable';\n }\n\n private getUserMultiplier(activity: number, trend: string): number {\n let base = Math.min(\n this.config.maxUserScaling,\n 1 + activity / this.config.highActivityThreshold,\n );\n\n // Adjust based on trend\n if (trend === 'increasing') base *= 1.2;\n if (trend === 'decreasing') base *= 0.8;\n\n return Math.max(1.0, base);\n }\n\n private getSustainedInactivityPeriod(requests: Array<number>): number {\n if (requests.length === 0) {\n // This should not be called when there are no requests (handled above)\n return 0;\n }\n\n const lastRequest = Math.max(...requests);\n return Date.now() - lastRequest;\n }\n}\n\n// Export types for use in other modules\nexport type { ActivityMetrics, DynamicCapacityResult };\n","import { HttpClientError } from '../errors/http-client-error.js';\nimport {\n CacheStore,\n DedupeStore,\n RateLimitStore,\n AdaptiveRateLimitStore,\n RequestPriority,\n hashRequest,\n} from '../stores/index.js';\nimport { HttpClientContract } from '../types/index.js';\n\nconst DEFAULT_RATE_LIMIT_HEADER_NAMES = {\n retryAfter: ['retry-after'],\n limit: ['ratelimit-limit', 'x-ratelimit-limit', 'rate-limit-limit'],\n remaining: [\n 'ratelimit-remaining',\n 'x-ratelimit-remaining',\n 'rate-limit-remaining',\n ],\n reset: ['ratelimit-reset', 'x-ratelimit-reset', 'rate-limit-reset'],\n combined: ['ratelimit'],\n} as const;\n\n/**\n * Wait for a specified period while supporting cancellation via AbortSignal.\n *\n * If the signal is aborted before the timeout completes the promise rejects\n * with an `Error` whose name is set to `AbortError`, mimicking DOMException in\n * browser environments without depending on it. This allows callers to use a\n * single `AbortController` for both the rate-limit wait *and* the subsequent\n * HTTP request.\n */\nfunction wait(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n if (signal) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n function onAbort() {\n clearTimeout(timer);\n const err = new Error('Aborted');\n err.name = 'AbortError';\n reject(err);\n }\n\n if (signal) {\n if (signal.aborted) {\n onAbort();\n } else {\n signal.addEventListener('abort', onAbort, { once: true });\n }\n }\n });\n}\n\nexport interface HttpClientStores {\n cache?: CacheStore;\n dedupe?: DedupeStore;\n rateLimit?: RateLimitStore | AdaptiveRateLimitStore;\n}\n\nexport interface HttpClientOptions {\n /**\n * Default cache TTL in seconds\n */\n defaultCacheTTL?: number;\n /**\n * Whether to throw errors on rate limit violations\n */\n throwOnRateLimit?: boolean;\n /**\n * Maximum time to wait for rate limit in milliseconds\n */\n maxWaitTime?: number;\n /**\n * Optional response transformer applied to the raw response data.\n * Use this for converting snake_case to camelCase, etc.\n */\n responseTransformer?: (data: unknown) => unknown;\n /**\n * Optional error handler to convert errors into domain-specific error types.\n * If not provided, a generic HttpClientError is thrown.\n */\n errorHandler?: (error: unknown) => Error;\n /**\n * Optional response validator/handler called after transformation.\n * Use this to inspect the response and throw domain-specific errors\n * based on response content (e.g., API-level error codes).\n */\n responseHandler?: (data: unknown) => unknown;\n /**\n * Configure rate-limit response header names for standards and custom APIs.\n */\n rateLimitHeaders?: {\n retryAfter?: Array<string>;\n limit?: Array<string>;\n remaining?: Array<string>;\n reset?: Array<string>;\n combined?: Array<string>;\n };\n}\n\ninterface RateLimitHeaderConfig {\n retryAfter: Array<string>;\n limit: Array<string>;\n remaining: Array<string>;\n reset: Array<string>;\n combined: Array<string>;\n}\n\ninterface ParsedResponseBody {\n data: unknown;\n}\n\ntype ErrorWithResponse = {\n message: string;\n response: {\n status: number;\n data: unknown;\n headers: Headers;\n };\n};\n\nexport class HttpClient implements HttpClientContract {\n private stores: HttpClientStores;\n private serverCooldowns = new Map<string, number>();\n private options: Required<\n Pick<\n HttpClientOptions,\n 'defaultCacheTTL' | 'throwOnRateLimit' | 'maxWaitTime'\n >\n > &\n Pick<\n HttpClientOptions,\n 'responseTransformer' | 'errorHandler' | 'responseHandler'\n > & {\n rateLimitHeaders: RateLimitHeaderConfig;\n };\n\n constructor(stores: HttpClientStores = {}, options: HttpClientOptions = {}) {\n this.stores = stores;\n this.options = {\n defaultCacheTTL: options.defaultCacheTTL ?? 3600,\n throwOnRateLimit: options.throwOnRateLimit ?? true,\n maxWaitTime: options.maxWaitTime ?? 60000,\n responseTransformer: options.responseTransformer,\n errorHandler: options.errorHandler,\n responseHandler: options.responseHandler,\n rateLimitHeaders: this.normalizeRateLimitHeaders(\n options.rateLimitHeaders,\n ),\n };\n }\n\n private normalizeRateLimitHeaders(\n customHeaders?: HttpClientOptions['rateLimitHeaders'],\n ): RateLimitHeaderConfig {\n return {\n retryAfter: this.normalizeHeaderNames(\n customHeaders?.retryAfter,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.retryAfter,\n ),\n limit: this.normalizeHeaderNames(\n customHeaders?.limit,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.limit,\n ),\n remaining: this.normalizeHeaderNames(\n customHeaders?.remaining,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.remaining,\n ),\n reset: this.normalizeHeaderNames(\n customHeaders?.reset,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.reset,\n ),\n combined: this.normalizeHeaderNames(\n customHeaders?.combined,\n DEFAULT_RATE_LIMIT_HEADER_NAMES.combined,\n ),\n };\n }\n\n private normalizeHeaderNames(\n providedNames: Array<string> | undefined,\n defaultNames: ReadonlyArray<string>,\n ): Array<string> {\n if (!providedNames || providedNames.length === 0) {\n return [...defaultNames];\n }\n\n const customNames = providedNames\n .map((name) => name.trim().toLowerCase())\n .filter(Boolean);\n\n if (customNames.length === 0) {\n return [...defaultNames];\n }\n\n return [...new Set([...customNames, ...defaultNames])];\n }\n\n /**\n * Infer the resource name from the endpoint URL\n * @param url The full URL or endpoint path\n * @returns The resource name for rate limiting\n */\n private inferResource(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use the first meaningful path segment as the resource name\n const segments = urlObj.pathname.split('/').filter(Boolean);\n return segments[segments.length - 1] || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Extract endpoint and params from URL for request hashing\n * @param url The full URL\n * @returns Object with endpoint and params for hashing\n */\n private parseUrlForHashing(url: string): {\n endpoint: string;\n params: Record<string, unknown>;\n } {\n const urlObj = new URL(url);\n const endpoint = `${urlObj.origin}${urlObj.pathname}`;\n const params: Record<string, unknown> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n const existing = params[key];\n\n // Keep repeated query keys as arrays so semantically distinct URLs like\n // `?tag=a&tag=b` and `?tag=b` do not hash to the same cache/dedupe key.\n if (existing === undefined) {\n params[key] = value;\n return;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n return;\n }\n\n params[key] = [existing, value];\n });\n\n return { endpoint, params };\n }\n\n private getOriginScope(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return 'unknown';\n }\n }\n\n private getHeaderValue(\n headers: Headers | Record<string, unknown> | undefined,\n names: Array<string>,\n ): string | undefined {\n if (!headers) {\n return undefined;\n }\n\n if (headers instanceof Headers) {\n for (const rawName of names) {\n const value = headers.get(rawName);\n if (value !== null) {\n return value;\n }\n }\n return undefined;\n }\n\n for (const rawName of names) {\n const name = rawName.toLowerCase();\n const value = headers[name] ?? headers[rawName];\n\n if (typeof value === 'string') {\n return value;\n }\n\n if (Array.isArray(value) && value.length > 0) {\n const first = value.find((entry) => typeof entry === 'string');\n if (typeof first === 'string') {\n return first;\n }\n }\n }\n\n return undefined;\n }\n\n private parseIntegerHeader(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const parsed = Number.parseInt(value.trim(), 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return undefined;\n }\n\n return parsed;\n }\n\n private parseRetryAfterMs(value: string | undefined): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const numeric = Number.parseInt(value.trim(), 10);\n if (Number.isFinite(numeric) && numeric >= 0) {\n return numeric * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (!Number.isFinite(dateMs)) {\n return undefined;\n }\n\n return Math.max(0, dateMs - Date.now());\n }\n\n private parseResetMs(value: string | undefined): number | undefined {\n const parsed = this.parseIntegerHeader(value);\n if (parsed === undefined) {\n return undefined;\n }\n\n if (parsed === 0) {\n return 0;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n\n if (parsed > nowSeconds + 1) {\n return Math.max(0, (parsed - nowSeconds) * 1000);\n }\n\n return parsed * 1000;\n }\n\n private parseCombinedRateLimitHeader(value: string | undefined): {\n remaining?: number;\n resetMs?: number;\n } {\n if (!value) {\n return {};\n }\n\n const remainingMatch = value.match(/(?:^|[;,])\\s*r\\s*=\\s*(\\d+)/i);\n const resetMatch = value.match(/(?:^|[;,])\\s*t\\s*=\\s*(\\d+)/i);\n\n return {\n remaining: remainingMatch\n ? this.parseIntegerHeader(remainingMatch[1])\n : undefined,\n resetMs: resetMatch ? this.parseResetMs(resetMatch[1]) : undefined,\n };\n }\n\n private applyServerRateLimitHints(\n url: string,\n headers: Headers | Record<string, unknown> | undefined,\n statusCode?: number,\n ): void {\n if (!headers) {\n return;\n }\n\n const config = this.options.rateLimitHeaders;\n const retryAfterRaw = this.getHeaderValue(headers, config.retryAfter);\n const resetRaw = this.getHeaderValue(headers, config.reset);\n const remainingRaw = this.getHeaderValue(headers, config.remaining);\n const combinedRaw = this.getHeaderValue(headers, config.combined);\n\n const retryAfterMs = this.parseRetryAfterMs(retryAfterRaw);\n const resetMs = this.parseResetMs(resetRaw);\n const remaining = this.parseIntegerHeader(remainingRaw);\n const combined = this.parseCombinedRateLimitHeader(combinedRaw);\n\n const effectiveRemaining = remaining ?? combined.remaining;\n const effectiveResetMs = resetMs ?? combined.resetMs;\n const hasRateLimitErrorStatus = statusCode === 429 || statusCode === 503;\n\n let waitMs: number | undefined;\n\n if (retryAfterMs !== undefined) {\n waitMs = retryAfterMs;\n } else if (\n effectiveResetMs !== undefined &&\n (hasRateLimitErrorStatus ||\n (effectiveRemaining !== undefined && effectiveRemaining <= 0))\n ) {\n waitMs = effectiveResetMs;\n }\n\n if (waitMs === undefined || waitMs <= 0) {\n return;\n }\n\n const scope = this.getOriginScope(url);\n this.serverCooldowns.set(scope, Date.now() + waitMs);\n }\n\n private async enforceServerCooldown(\n url: string,\n signal?: AbortSignal,\n ): Promise<void> {\n const scope = this.getOriginScope(url);\n const startedAt = Date.now();\n\n // Re-check cooldown after each sleep so we never proceed while a server\n // cooldown is still active. This avoids bypassing limits when cooldown\n // duration is longer than maxWaitTime.\n while (true) {\n const cooldownUntil = this.serverCooldowns.get(scope);\n if (!cooldownUntil) {\n return;\n }\n\n const waitMs = cooldownUntil - Date.now();\n if (waitMs <= 0) {\n this.serverCooldowns.delete(scope);\n return;\n }\n\n if (this.options.throwOnRateLimit) {\n throw new Error(\n `Rate limit exceeded for origin '${scope}'. Wait ${waitMs}ms before retrying.`,\n );\n }\n\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for origin '${scope}'.`,\n );\n }\n\n await wait(Math.min(waitMs, remainingWaitBudgetMs), signal);\n }\n }\n\n private async enforceStoreRateLimit(\n resource: string,\n priority: RequestPriority,\n signal?: AbortSignal,\n ): Promise<boolean> {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n const startedAt = Date.now();\n const hasAtomicAcquire = typeof rateLimit.acquire === 'function';\n\n const canProceedNow = async (): Promise<boolean> => {\n if (hasAtomicAcquire) {\n return rateLimit.acquire!(resource, priority);\n }\n return rateLimit.canProceed(resource, priority);\n };\n\n if (this.options.throwOnRateLimit) {\n const canProceed = await canProceedNow();\n if (!canProceed) {\n const waitTime = await rateLimit.getWaitTime(resource, priority);\n throw new Error(\n `Rate limit exceeded for resource '${resource}'. Wait ${waitTime}ms before retrying.`,\n );\n }\n return hasAtomicAcquire;\n }\n\n // Keep polling + waiting until the store explicitly allows the request or\n // we exhaust maxWaitTime. A single one-off sleep can otherwise let a request\n // through while still over limit.\n while (!(await canProceedNow())) {\n const suggestedWaitMs = await rateLimit.getWaitTime(resource, priority);\n const elapsedMs = Date.now() - startedAt;\n const remainingWaitBudgetMs = this.options.maxWaitTime - elapsedMs;\n\n if (remainingWaitBudgetMs <= 0) {\n throw new Error(\n `Rate limit wait exceeded maxWaitTime (${this.options.maxWaitTime}ms) for resource '${resource}'.`,\n );\n }\n\n // If a store reports \"blocked\" but no wait time, use a tiny backoff to\n // avoid a tight CPU loop while still converging quickly.\n const waitTime =\n suggestedWaitMs > 0\n ? Math.min(suggestedWaitMs, remainingWaitBudgetMs)\n : Math.min(25, remainingWaitBudgetMs);\n\n await wait(waitTime, signal);\n }\n\n return hasAtomicAcquire;\n }\n\n private generateClientError(err: unknown): Error {\n // If a custom error handler is provided, use it\n if (this.options.errorHandler) {\n return this.options.errorHandler(err);\n }\n\n if (err instanceof HttpClientError) {\n return err;\n }\n\n const responseError = err as Partial<ErrorWithResponse>;\n const statusCode =\n typeof responseError.response?.status === 'number'\n ? responseError.response.status\n : undefined;\n\n const responseData = responseError.response?.data;\n const derivedResponseMessage =\n typeof responseData === 'object' && responseData !== null\n ? (responseData as { message?: unknown }).message\n : undefined;\n const responseMessage =\n typeof derivedResponseMessage === 'string'\n ? derivedResponseMessage\n : undefined;\n\n const errorMessage =\n err instanceof Error\n ? err.message\n : typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Unknown error';\n const message = `${errorMessage}${responseMessage ? `, ${responseMessage}` : ''}`;\n\n return new HttpClientError(message, statusCode);\n }\n\n private async parseResponseBody(\n response: Response,\n ): Promise<ParsedResponseBody> {\n if (response.status === 204 || response.status === 205) {\n return { data: undefined };\n }\n\n const rawBody = await response.text();\n if (!rawBody) {\n return { data: undefined };\n }\n\n const contentType =\n response.headers.get('content-type')?.toLowerCase() ?? '';\n const shouldAttemptJsonParsing =\n contentType.includes('application/json') ||\n contentType.includes('+json') ||\n rawBody.trimStart().startsWith('{') ||\n rawBody.trimStart().startsWith('[');\n\n if (!shouldAttemptJsonParsing) {\n return { data: rawBody };\n }\n\n try {\n const parsed = JSON.parse(rawBody) as unknown;\n if (typeof parsed === 'object' && parsed !== null) {\n return { data: parsed };\n }\n\n return { data: parsed };\n } catch {\n return { data: rawBody };\n }\n }\n\n async get<Result>(\n url: string,\n options: { signal?: AbortSignal; priority?: RequestPriority } = {},\n ): Promise<Result> {\n const { signal, priority = 'background' } = options;\n const { endpoint, params } = this.parseUrlForHashing(url);\n const hash = hashRequest(endpoint, params);\n const resource = this.inferResource(url);\n\n try {\n await this.enforceServerCooldown(url, signal);\n\n // 1. Cache - check for cached response\n if (this.stores.cache) {\n const cachedResult = await this.stores.cache.get(hash);\n if (cachedResult !== undefined) {\n return cachedResult as Result;\n }\n }\n\n // 2. Deduplication - check for in-progress request\n if (this.stores.dedupe) {\n const existingResult = await this.stores.dedupe.waitFor(hash);\n if (existingResult !== undefined) {\n return existingResult as Result;\n }\n\n if (this.stores.dedupe.registerOrJoin) {\n const registration = await this.stores.dedupe.registerOrJoin(hash);\n\n if (!registration.isOwner) {\n const joinedResult = await this.stores.dedupe.waitFor(hash);\n if (joinedResult !== undefined) {\n return joinedResult as Result;\n }\n }\n } else {\n await this.stores.dedupe.register(hash);\n }\n }\n\n // 3. Rate limiting - check if request can proceed\n let alreadyRecordedRateLimit = false;\n if (this.stores.rateLimit) {\n alreadyRecordedRateLimit = await this.enforceStoreRateLimit(\n resource,\n priority,\n signal,\n );\n }\n\n // 4. Execute the actual HTTP request\n const response = await fetch(url, { signal });\n this.applyServerRateLimitHints(url, response.headers, response.status);\n\n const parsedBody = await this.parseResponseBody(response);\n\n if (!response.ok) {\n const error: ErrorWithResponse = {\n message: `Request failed with status ${response.status}`,\n response: {\n status: response.status,\n data: parsedBody.data,\n headers: response.headers,\n },\n };\n throw error;\n }\n\n // 5. Apply response transformer if provided\n let data: unknown = parsedBody.data;\n if (this.options.responseTransformer && data) {\n data = this.options.responseTransformer(data);\n }\n\n // 6. Apply response handler if provided (for domain-specific validation)\n if (this.options.responseHandler) {\n data = this.options.responseHandler(data);\n }\n\n const result = data as Result;\n\n // 7. Record the request for rate limiting\n if (this.stores.rateLimit && !alreadyRecordedRateLimit) {\n const rateLimit = this.stores.rateLimit as AdaptiveRateLimitStore;\n await rateLimit.record(resource, priority);\n }\n\n // 8. Cache the result\n if (this.stores.cache) {\n await this.stores.cache.set(hash, result, this.options.defaultCacheTTL);\n }\n\n // 9. Mark deduplication as complete\n if (this.stores.dedupe) {\n await this.stores.dedupe.complete(hash, result);\n }\n\n return result;\n } catch (error) {\n // Mark deduplication as failed\n if (this.stores.dedupe) {\n await this.stores.dedupe.fail(hash, error as Error);\n }\n\n // Allow callers to detect aborts distinctly – do not wrap AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n throw error;\n }\n\n // Already a processed error from the !response.ok branch above\n if (error instanceof HttpClientError) {\n throw error;\n }\n\n throw this.generateClientError(error);\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"@repo/vitest-config": "0.0.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"axios": "1.13.5",
|
|
26
25
|
"zod": "3.25.71"
|
|
27
26
|
},
|
|
28
27
|
"keywords": [
|
|
@@ -42,7 +41,7 @@
|
|
|
42
41
|
"lib/**"
|
|
43
42
|
],
|
|
44
43
|
"homepage": "https://github.com/AllyMurray/http-client-toolkit#readme",
|
|
45
|
-
"version": "0.0
|
|
44
|
+
"version": "0.1.0",
|
|
46
45
|
"bugs": {
|
|
47
46
|
"url": "https://github.com/AllyMurray/http-client-toolkit/issues"
|
|
48
47
|
},
|