@lgriffin/esi.ts 8.0.0 → 9.0.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/CHANGELOG.md +27 -0
- package/LICENSE +26 -26
- package/README.md +986 -967
- package/dist/config/jest/globalSetup.d.ts.map +1 -1
- package/dist/config/jest/globalTeardown.d.ts.map +1 -1
- package/dist/core/configureApiClient.d.ts.map +1 -1
- package/dist/core/pagination/CursorPaginationHandler.d.ts +9 -4
- package/dist/core/pagination/CursorPaginationHandler.d.ts.map +1 -1
- package/dist/core/pagination/PaginationHandler.d.ts +6 -5
- package/dist/core/pagination/PaginationHandler.d.ts.map +1 -1
- package/dist/core/requestPipeline/cachePolicy.d.ts +1 -0
- package/dist/core/requestPipeline/cachePolicy.d.ts.map +1 -1
- package/dist/core/requestPipeline/paginationOrchestration.d.ts.map +1 -1
- package/dist/core/requestPipeline/statusHandling.d.ts.map +1 -1
- package/dist/core/util/error.d.ts +2 -0
- package/dist/core/util/error.d.ts.map +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +196 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.mjs +146 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +400 -389
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +399 -389
- package/dist/index.mjs.map +1 -1
- package/dist/schemas/index.js +2087 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/index.mjs +1914 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +935 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/index.mjs +898 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/types/generated/esi-spec.generated.d.ts +3 -3
- package/dist/types/generated/esi-spec.generated.d.ts.map +1 -1
- package/package.json +253 -235
- package/dist/schemas/generated/alliance.generated.d.ts +0 -15
- package/dist/schemas/generated/alliance.generated.d.ts.map +0 -1
- package/dist/schemas/generated/assets.generated.d.ts +0 -272
- package/dist/schemas/generated/assets.generated.d.ts.map +0 -1
- package/dist/schemas/generated/calendar.generated.d.ts +0 -41
- package/dist/schemas/generated/calendar.generated.d.ts.map +0 -1
- package/dist/schemas/generated/character.generated.d.ts +0 -670
- package/dist/schemas/generated/character.generated.d.ts.map +0 -1
- package/dist/schemas/generated/clones.generated.d.ts +0 -23
- package/dist/schemas/generated/clones.generated.d.ts.map +0 -1
- package/dist/schemas/generated/contacts.generated.d.ts +0 -50
- package/dist/schemas/generated/contacts.generated.d.ts.map +0 -1
- package/dist/schemas/generated/contracts.generated.d.ts +0 -162
- package/dist/schemas/generated/contracts.generated.d.ts.map +0 -1
- package/dist/schemas/generated/corporation-projects.generated.d.ts +0 -89
- package/dist/schemas/generated/corporation-projects.generated.d.ts.map +0 -1
- package/dist/schemas/generated/corporation.generated.d.ts +0 -1515
- package/dist/schemas/generated/corporation.generated.d.ts.map +0 -1
- package/dist/schemas/generated/dogma.generated.d.ts +0 -57
- package/dist/schemas/generated/dogma.generated.d.ts.map +0 -1
- package/dist/schemas/generated/faction-warfare.generated.d.ts +0 -155
- package/dist/schemas/generated/faction-warfare.generated.d.ts.map +0 -1
- package/dist/schemas/generated/fittings.generated.d.ts +0 -57
- package/dist/schemas/generated/fittings.generated.d.ts.map +0 -1
- package/dist/schemas/generated/fleets.generated.d.ts +0 -45
- package/dist/schemas/generated/fleets.generated.d.ts.map +0 -1
- package/dist/schemas/generated/freelance-jobs.generated.d.ts +0 -173
- package/dist/schemas/generated/freelance-jobs.generated.d.ts.map +0 -1
- package/dist/schemas/generated/incursions.generated.d.ts +0 -16
- package/dist/schemas/generated/incursions.generated.d.ts.map +0 -1
- package/dist/schemas/generated/index.d.ts +0 -33
- package/dist/schemas/generated/index.d.ts.map +0 -1
- package/dist/schemas/generated/industry.generated.d.ts +0 -117
- package/dist/schemas/generated/industry.generated.d.ts.map +0 -1
- package/dist/schemas/generated/insurance.generated.d.ts +0 -10
- package/dist/schemas/generated/insurance.generated.d.ts.map +0 -1
- package/dist/schemas/generated/killmails.generated.d.ts +0 -55
- package/dist/schemas/generated/killmails.generated.d.ts.map +0 -1
- package/dist/schemas/generated/location.generated.d.ts +0 -18
- package/dist/schemas/generated/location.generated.d.ts.map +0 -1
- package/dist/schemas/generated/loyalty.generated.d.ts +0 -18
- package/dist/schemas/generated/loyalty.generated.d.ts.map +0 -1
- package/dist/schemas/generated/mail.generated.d.ts +0 -68
- package/dist/schemas/generated/mail.generated.d.ts.map +0 -1
- package/dist/schemas/generated/market.generated.d.ts +0 -201
- package/dist/schemas/generated/market.generated.d.ts.map +0 -1
- package/dist/schemas/generated/meta.generated.d.ts +0 -26
- package/dist/schemas/generated/meta.generated.d.ts.map +0 -1
- package/dist/schemas/generated/planetary-interaction.generated.d.ts +0 -90
- package/dist/schemas/generated/planetary-interaction.generated.d.ts.map +0 -1
- package/dist/schemas/generated/routes.generated.d.ts +0 -5
- package/dist/schemas/generated/routes.generated.d.ts.map +0 -1
- package/dist/schemas/generated/search.generated.d.ts +0 -15
- package/dist/schemas/generated/search.generated.d.ts.map +0 -1
- package/dist/schemas/generated/skills.generated.d.ts +0 -32
- package/dist/schemas/generated/skills.generated.d.ts.map +0 -1
- package/dist/schemas/generated/sovereignty.generated.d.ts +0 -37
- package/dist/schemas/generated/sovereignty.generated.d.ts.map +0 -1
- package/dist/schemas/generated/status.generated.d.ts +0 -8
- package/dist/schemas/generated/status.generated.d.ts.map +0 -1
- package/dist/schemas/generated/universe.generated.d.ts +0 -394
- package/dist/schemas/generated/universe.generated.d.ts.map +0 -1
- package/dist/schemas/generated/wallet.generated.d.ts +0 -411
- package/dist/schemas/generated/wallet.generated.d.ts.map +0 -1
- package/dist/schemas/generated/wars.generated.d.ts +0 -31
- package/dist/schemas/generated/wars.generated.d.ts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -205,6 +205,216 @@ var ApiClient = class {
|
|
|
205
205
|
}
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
+
// src/core/logger/logger.ts
|
|
209
|
+
import pino from "pino";
|
|
210
|
+
var pinoLogger = pino({ level: process.env.ESI_LOG_LEVEL || "warn" });
|
|
211
|
+
var logger = {
|
|
212
|
+
info: (msg) => {
|
|
213
|
+
pinoLogger.info(msg);
|
|
214
|
+
},
|
|
215
|
+
warn: (msg) => {
|
|
216
|
+
pinoLogger.warn(msg);
|
|
217
|
+
},
|
|
218
|
+
error: (msg) => {
|
|
219
|
+
pinoLogger.error(msg);
|
|
220
|
+
},
|
|
221
|
+
debug: (msg) => {
|
|
222
|
+
pinoLogger.debug(msg);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var logger_default = logger;
|
|
226
|
+
|
|
227
|
+
// src/core/logger/loggerUtil.ts
|
|
228
|
+
var activeLogger = logger_default;
|
|
229
|
+
var setLogger = (customLogger) => {
|
|
230
|
+
activeLogger = customLogger;
|
|
231
|
+
};
|
|
232
|
+
var logInfo = (message) => {
|
|
233
|
+
activeLogger.info(message);
|
|
234
|
+
};
|
|
235
|
+
var logError = (message) => {
|
|
236
|
+
activeLogger.error(message);
|
|
237
|
+
};
|
|
238
|
+
var logWarn = (message) => {
|
|
239
|
+
activeLogger.warn(message);
|
|
240
|
+
};
|
|
241
|
+
var logDebug = (message) => {
|
|
242
|
+
activeLogger.debug(message);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/core/circuitBreaker/CircuitBreaker.ts
|
|
246
|
+
var CircuitBreaker = class {
|
|
247
|
+
circuits = /* @__PURE__ */ new Map();
|
|
248
|
+
failureThreshold;
|
|
249
|
+
resetTimeoutMs;
|
|
250
|
+
halfOpenMaxAttempts;
|
|
251
|
+
staleThresholdMs;
|
|
252
|
+
keyStrategy;
|
|
253
|
+
cleanupTimer = null;
|
|
254
|
+
constructor(config = {}) {
|
|
255
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
256
|
+
this.resetTimeoutMs = config.resetTimeoutMs ?? 3e4;
|
|
257
|
+
this.halfOpenMaxAttempts = config.halfOpenMaxAttempts ?? 1;
|
|
258
|
+
this.staleThresholdMs = config.staleThresholdMs ?? 36e5;
|
|
259
|
+
this.keyStrategy = config.keyStrategy ?? "resolved";
|
|
260
|
+
const cleanupIntervalMs = config.cleanupIntervalMs;
|
|
261
|
+
if (cleanupIntervalMs !== void 0 && cleanupIntervalMs > 0) {
|
|
262
|
+
this.startCleanupTimer(cleanupIntervalMs);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
getKey(endpoint) {
|
|
266
|
+
return endpoint.split("?")[0];
|
|
267
|
+
}
|
|
268
|
+
getOrCreate(key) {
|
|
269
|
+
let record = this.circuits.get(key);
|
|
270
|
+
if (!record) {
|
|
271
|
+
record = {
|
|
272
|
+
state: "closed",
|
|
273
|
+
failures: 0,
|
|
274
|
+
lastFailureTime: 0,
|
|
275
|
+
halfOpenAttempts: 0
|
|
276
|
+
};
|
|
277
|
+
this.circuits.set(key, record);
|
|
278
|
+
}
|
|
279
|
+
return record;
|
|
280
|
+
}
|
|
281
|
+
checkCircuit(endpoint) {
|
|
282
|
+
const key = this.getKey(endpoint);
|
|
283
|
+
const record = this.getOrCreate(key);
|
|
284
|
+
if (record.state === "closed") return;
|
|
285
|
+
if (record.state === "open") {
|
|
286
|
+
const elapsed = Date.now() - record.lastFailureTime;
|
|
287
|
+
if (elapsed >= this.resetTimeoutMs) {
|
|
288
|
+
record.state = "half-open";
|
|
289
|
+
record.halfOpenAttempts = 0;
|
|
290
|
+
logInfo(`Circuit half-open for ${key}, allowing probe request`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const remainingMs = this.resetTimeoutMs - elapsed;
|
|
294
|
+
throw new CircuitOpenError(key, record.failures, remainingMs);
|
|
295
|
+
}
|
|
296
|
+
if (record.state === "half-open") {
|
|
297
|
+
if (record.halfOpenAttempts >= this.halfOpenMaxAttempts) {
|
|
298
|
+
throw new CircuitOpenError(key, record.failures, this.resetTimeoutMs);
|
|
299
|
+
}
|
|
300
|
+
record.halfOpenAttempts++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
recordSuccess(endpoint) {
|
|
304
|
+
const key = this.getKey(endpoint);
|
|
305
|
+
const record = this.circuits.get(key);
|
|
306
|
+
if (!record) return;
|
|
307
|
+
if (record.state === "half-open") {
|
|
308
|
+
logInfo(`Circuit closed for ${key} after successful probe`);
|
|
309
|
+
}
|
|
310
|
+
record.state = "closed";
|
|
311
|
+
record.failures = 0;
|
|
312
|
+
record.halfOpenAttempts = 0;
|
|
313
|
+
}
|
|
314
|
+
recordFailure(endpoint, statusCode) {
|
|
315
|
+
if (statusCode !== 0 && statusCode < 500 && statusCode !== 420 && statusCode !== 429) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const key = this.getKey(endpoint);
|
|
319
|
+
const record = this.getOrCreate(key);
|
|
320
|
+
record.failures++;
|
|
321
|
+
record.lastFailureTime = Date.now();
|
|
322
|
+
if (record.state === "half-open") {
|
|
323
|
+
record.state = "open";
|
|
324
|
+
logWarn(
|
|
325
|
+
`Circuit re-opened for ${key} after failed probe (${record.failures} failures)`
|
|
326
|
+
);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (record.failures >= this.failureThreshold) {
|
|
330
|
+
record.state = "open";
|
|
331
|
+
logWarn(
|
|
332
|
+
`Circuit opened for ${key} after ${record.failures} consecutive failures`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
getState(endpoint) {
|
|
337
|
+
const key = this.getKey(endpoint);
|
|
338
|
+
const record = this.circuits.get(key);
|
|
339
|
+
if (!record) return "closed";
|
|
340
|
+
if (record.state === "open") {
|
|
341
|
+
const elapsed = Date.now() - record.lastFailureTime;
|
|
342
|
+
if (elapsed >= this.resetTimeoutMs) {
|
|
343
|
+
return "half-open";
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return record.state;
|
|
347
|
+
}
|
|
348
|
+
getStats() {
|
|
349
|
+
const circuits = {};
|
|
350
|
+
let openCircuits = 0;
|
|
351
|
+
for (const [key, record] of this.circuits.entries()) {
|
|
352
|
+
const state = this.getState(key);
|
|
353
|
+
circuits[key] = { state, failures: record.failures };
|
|
354
|
+
if (state === "open") openCircuits++;
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
totalCircuits: this.circuits.size,
|
|
358
|
+
openCircuits,
|
|
359
|
+
circuits
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
reset(endpoint) {
|
|
363
|
+
if (endpoint) {
|
|
364
|
+
this.circuits.delete(this.getKey(endpoint));
|
|
365
|
+
} else {
|
|
366
|
+
this.circuits.clear();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
cleanup() {
|
|
370
|
+
const now = Date.now();
|
|
371
|
+
let cleaned = 0;
|
|
372
|
+
for (const [key, record] of this.circuits.entries()) {
|
|
373
|
+
if (record.state === "closed" && record.failures === 0 && record.lastFailureTime > 0 && now - record.lastFailureTime > this.staleThresholdMs) {
|
|
374
|
+
this.circuits.delete(key);
|
|
375
|
+
cleaned++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (cleaned > 0) {
|
|
379
|
+
logInfo(`Circuit breaker cleanup: removed ${cleaned} stale circuits`);
|
|
380
|
+
}
|
|
381
|
+
return cleaned;
|
|
382
|
+
}
|
|
383
|
+
getKeyStrategy() {
|
|
384
|
+
return this.keyStrategy;
|
|
385
|
+
}
|
|
386
|
+
destroy() {
|
|
387
|
+
if (this.cleanupTimer) {
|
|
388
|
+
clearInterval(this.cleanupTimer);
|
|
389
|
+
this.cleanupTimer = null;
|
|
390
|
+
}
|
|
391
|
+
this.circuits.clear();
|
|
392
|
+
}
|
|
393
|
+
shutdown() {
|
|
394
|
+
this.destroy();
|
|
395
|
+
}
|
|
396
|
+
startCleanupTimer(intervalMs) {
|
|
397
|
+
this.cleanupTimer = setInterval(() => {
|
|
398
|
+
this.cleanup();
|
|
399
|
+
}, intervalMs);
|
|
400
|
+
this.cleanupTimer.unref();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var CircuitOpenError = class extends Error {
|
|
404
|
+
endpoint;
|
|
405
|
+
failures;
|
|
406
|
+
retryAfterMs;
|
|
407
|
+
constructor(endpoint, failures, retryAfterMs) {
|
|
408
|
+
super(
|
|
409
|
+
`Circuit breaker open for ${endpoint} after ${failures} failures (retry after ${Math.ceil(retryAfterMs / 1e3)}s)`
|
|
410
|
+
);
|
|
411
|
+
this.name = "CircuitOpenError";
|
|
412
|
+
this.endpoint = endpoint;
|
|
413
|
+
this.failures = failures;
|
|
414
|
+
this.retryAfterMs = retryAfterMs;
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
208
418
|
// src/core/util/error.ts
|
|
209
419
|
var SENSITIVE_PARAMS = [
|
|
210
420
|
"token",
|
|
@@ -299,6 +509,9 @@ function isTimeout(error) {
|
|
|
299
509
|
function isRetryable(error) {
|
|
300
510
|
return error instanceof EsiError && error.retryable;
|
|
301
511
|
}
|
|
512
|
+
function isCircuitOpen(error) {
|
|
513
|
+
return error instanceof CircuitOpenError;
|
|
514
|
+
}
|
|
302
515
|
var buildError = (message, type = "ERROR", url) => {
|
|
303
516
|
const error = new Error(`[${type}] ${message}`);
|
|
304
517
|
if (url) {
|
|
@@ -322,43 +535,6 @@ function isValidationError(error) {
|
|
|
322
535
|
return error instanceof EsiValidationError;
|
|
323
536
|
}
|
|
324
537
|
|
|
325
|
-
// src/core/logger/logger.ts
|
|
326
|
-
import pino from "pino";
|
|
327
|
-
var pinoLogger = pino({ level: process.env.ESI_LOG_LEVEL || "warn" });
|
|
328
|
-
var logger = {
|
|
329
|
-
info: (msg) => {
|
|
330
|
-
pinoLogger.info(msg);
|
|
331
|
-
},
|
|
332
|
-
warn: (msg) => {
|
|
333
|
-
pinoLogger.warn(msg);
|
|
334
|
-
},
|
|
335
|
-
error: (msg) => {
|
|
336
|
-
pinoLogger.error(msg);
|
|
337
|
-
},
|
|
338
|
-
debug: (msg) => {
|
|
339
|
-
pinoLogger.debug(msg);
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
var logger_default = logger;
|
|
343
|
-
|
|
344
|
-
// src/core/logger/loggerUtil.ts
|
|
345
|
-
var activeLogger = logger_default;
|
|
346
|
-
var setLogger = (customLogger) => {
|
|
347
|
-
activeLogger = customLogger;
|
|
348
|
-
};
|
|
349
|
-
var logInfo = (message) => {
|
|
350
|
-
activeLogger.info(message);
|
|
351
|
-
};
|
|
352
|
-
var logError = (message) => {
|
|
353
|
-
activeLogger.error(message);
|
|
354
|
-
};
|
|
355
|
-
var logWarn = (message) => {
|
|
356
|
-
activeLogger.warn(message);
|
|
357
|
-
};
|
|
358
|
-
var logDebug = (message) => {
|
|
359
|
-
activeLogger.debug(message);
|
|
360
|
-
};
|
|
361
|
-
|
|
362
538
|
// src/core/constants.ts
|
|
363
539
|
var PACKAGE_NAME = "esi.ts";
|
|
364
540
|
var PACKAGE_VERSION = "7.4.0";
|
|
@@ -539,238 +715,67 @@ var esiCacheTtls = {
|
|
|
539
715
|
"GET:universe/system_kills": 3600,
|
|
540
716
|
"GET:wars": 3600,
|
|
541
717
|
"GET:wars/{war_id}": 3600,
|
|
542
|
-
"GET:wars/{war_id}/killmails": 3600
|
|
543
|
-
};
|
|
544
|
-
|
|
545
|
-
// src/core/requestPipeline/cachePolicy.ts
|
|
546
|
-
function lookupSpecTtl(method, templatePath) {
|
|
547
|
-
const normalized = templatePath.replace(/\/$/, "").replace(/\{(\w+)\}/g, (_, name) => `{${camelToSnake(name)}}`);
|
|
548
|
-
const key = `${method}:${normalized}`;
|
|
549
|
-
const seconds = esiCacheTtls[key];
|
|
550
|
-
return typeof seconds === "number" ? seconds * 1e3 : void 0;
|
|
551
|
-
}
|
|
552
|
-
function trySpecAwareCacheHit(client, url, method, templatePath, resolveCache2) {
|
|
553
|
-
if (method !== "GET" || !templatePath) return null;
|
|
554
|
-
const specTtlMs = lookupSpecTtl(method, templatePath);
|
|
555
|
-
if (!specTtlMs) return null;
|
|
556
|
-
const cache = resolveCache2(client);
|
|
557
|
-
if (!cache) return null;
|
|
558
|
-
const entry = cache.get(url);
|
|
559
|
-
if (!entry) return null;
|
|
560
|
-
const age = Date.now() - entry.timestamp;
|
|
561
|
-
if (age < specTtlMs) {
|
|
562
|
-
logDebug(
|
|
563
|
-
`Spec-aware cache hit for ${url} (age=${Math.round(age / 1e3)}s, ttl=${Math.round(specTtlMs / 1e3)}s)`
|
|
564
|
-
);
|
|
565
|
-
return {
|
|
566
|
-
headers: entry.headers,
|
|
567
|
-
body: entry.data,
|
|
568
|
-
fromCache: true,
|
|
569
|
-
cacheHitType: "spec-ttl"
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
function tryStaleCacheResponse(client, url, parsed, resolveCache2) {
|
|
575
|
-
const cache = resolveCache2(client);
|
|
576
|
-
if (!cache) return null;
|
|
577
|
-
const cachedEntry = cache.get(url);
|
|
578
|
-
if (!cachedEntry) return null;
|
|
579
|
-
return {
|
|
580
|
-
headers: { ...cachedEntry.headers, ...parsed.raw },
|
|
581
|
-
body: cachedEntry.data,
|
|
582
|
-
fromCache: true,
|
|
583
|
-
stale: true,
|
|
584
|
-
cacheHitType: "stale-on-error"
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
function cacheResponse(client, url, method, endpoint, parsed, data, useETag, resolveCache2, templatePath) {
|
|
588
|
-
const cache = resolveCache2(client);
|
|
589
|
-
if (useETag && method === "GET" && cache && parsed.etag) {
|
|
590
|
-
const headerTtl = parseCacheControlTtl(parsed.raw);
|
|
591
|
-
const specTtlMs = templatePath ? lookupSpecTtl(method, templatePath) : void 0;
|
|
592
|
-
const ttl = specTtlMs ?? headerTtl;
|
|
593
|
-
cache.set(url, parsed.etag, data, parsed.raw, ttl);
|
|
594
|
-
const ttlInfo = ttl ? ` (ttl=${ttl}ms)` : "";
|
|
595
|
-
logDebug(`Cached response for ${url} with ETag ${parsed.etag}${ttlInfo}`);
|
|
596
|
-
}
|
|
597
|
-
if (method !== "GET" && cache) {
|
|
598
|
-
cache.deleteByPath(endpoint.split("?")[0]);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// src/core/circuitBreaker/CircuitBreaker.ts
|
|
603
|
-
var CircuitBreaker = class {
|
|
604
|
-
circuits = /* @__PURE__ */ new Map();
|
|
605
|
-
failureThreshold;
|
|
606
|
-
resetTimeoutMs;
|
|
607
|
-
halfOpenMaxAttempts;
|
|
608
|
-
staleThresholdMs;
|
|
609
|
-
keyStrategy;
|
|
610
|
-
cleanupTimer = null;
|
|
611
|
-
constructor(config = {}) {
|
|
612
|
-
this.failureThreshold = config.failureThreshold ?? 5;
|
|
613
|
-
this.resetTimeoutMs = config.resetTimeoutMs ?? 3e4;
|
|
614
|
-
this.halfOpenMaxAttempts = config.halfOpenMaxAttempts ?? 1;
|
|
615
|
-
this.staleThresholdMs = config.staleThresholdMs ?? 36e5;
|
|
616
|
-
this.keyStrategy = config.keyStrategy ?? "resolved";
|
|
617
|
-
const cleanupIntervalMs = config.cleanupIntervalMs;
|
|
618
|
-
if (cleanupIntervalMs !== void 0 && cleanupIntervalMs > 0) {
|
|
619
|
-
this.startCleanupTimer(cleanupIntervalMs);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
getKey(endpoint) {
|
|
623
|
-
return endpoint.split("?")[0];
|
|
624
|
-
}
|
|
625
|
-
getOrCreate(key) {
|
|
626
|
-
let record = this.circuits.get(key);
|
|
627
|
-
if (!record) {
|
|
628
|
-
record = {
|
|
629
|
-
state: "closed",
|
|
630
|
-
failures: 0,
|
|
631
|
-
lastFailureTime: 0,
|
|
632
|
-
halfOpenAttempts: 0
|
|
633
|
-
};
|
|
634
|
-
this.circuits.set(key, record);
|
|
635
|
-
}
|
|
636
|
-
return record;
|
|
637
|
-
}
|
|
638
|
-
checkCircuit(endpoint) {
|
|
639
|
-
const key = this.getKey(endpoint);
|
|
640
|
-
const record = this.getOrCreate(key);
|
|
641
|
-
if (record.state === "closed") return;
|
|
642
|
-
if (record.state === "open") {
|
|
643
|
-
const elapsed = Date.now() - record.lastFailureTime;
|
|
644
|
-
if (elapsed >= this.resetTimeoutMs) {
|
|
645
|
-
record.state = "half-open";
|
|
646
|
-
record.halfOpenAttempts = 0;
|
|
647
|
-
logInfo(`Circuit half-open for ${key}, allowing probe request`);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
const remainingMs = this.resetTimeoutMs - elapsed;
|
|
651
|
-
throw new CircuitOpenError(key, record.failures, remainingMs);
|
|
652
|
-
}
|
|
653
|
-
if (record.state === "half-open") {
|
|
654
|
-
if (record.halfOpenAttempts >= this.halfOpenMaxAttempts) {
|
|
655
|
-
throw new CircuitOpenError(key, record.failures, this.resetTimeoutMs);
|
|
656
|
-
}
|
|
657
|
-
record.halfOpenAttempts++;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
recordSuccess(endpoint) {
|
|
661
|
-
const key = this.getKey(endpoint);
|
|
662
|
-
const record = this.circuits.get(key);
|
|
663
|
-
if (!record) return;
|
|
664
|
-
if (record.state === "half-open") {
|
|
665
|
-
logInfo(`Circuit closed for ${key} after successful probe`);
|
|
666
|
-
}
|
|
667
|
-
record.state = "closed";
|
|
668
|
-
record.failures = 0;
|
|
669
|
-
record.halfOpenAttempts = 0;
|
|
670
|
-
}
|
|
671
|
-
recordFailure(endpoint, statusCode) {
|
|
672
|
-
if (statusCode !== 0 && statusCode < 500 && statusCode !== 420 && statusCode !== 429) {
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
const key = this.getKey(endpoint);
|
|
676
|
-
const record = this.getOrCreate(key);
|
|
677
|
-
record.failures++;
|
|
678
|
-
record.lastFailureTime = Date.now();
|
|
679
|
-
if (record.state === "half-open") {
|
|
680
|
-
record.state = "open";
|
|
681
|
-
logWarn(
|
|
682
|
-
`Circuit re-opened for ${key} after failed probe (${record.failures} failures)`
|
|
683
|
-
);
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
if (record.failures >= this.failureThreshold) {
|
|
687
|
-
record.state = "open";
|
|
688
|
-
logWarn(
|
|
689
|
-
`Circuit opened for ${key} after ${record.failures} consecutive failures`
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
getState(endpoint) {
|
|
694
|
-
const key = this.getKey(endpoint);
|
|
695
|
-
const record = this.circuits.get(key);
|
|
696
|
-
if (!record) return "closed";
|
|
697
|
-
if (record.state === "open") {
|
|
698
|
-
const elapsed = Date.now() - record.lastFailureTime;
|
|
699
|
-
if (elapsed >= this.resetTimeoutMs) {
|
|
700
|
-
return "half-open";
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
return record.state;
|
|
704
|
-
}
|
|
705
|
-
getStats() {
|
|
706
|
-
const circuits = {};
|
|
707
|
-
let openCircuits = 0;
|
|
708
|
-
for (const [key, record] of this.circuits.entries()) {
|
|
709
|
-
const state = this.getState(key);
|
|
710
|
-
circuits[key] = { state, failures: record.failures };
|
|
711
|
-
if (state === "open") openCircuits++;
|
|
712
|
-
}
|
|
713
|
-
return {
|
|
714
|
-
totalCircuits: this.circuits.size,
|
|
715
|
-
openCircuits,
|
|
716
|
-
circuits
|
|
717
|
-
};
|
|
718
|
-
}
|
|
719
|
-
reset(endpoint) {
|
|
720
|
-
if (endpoint) {
|
|
721
|
-
this.circuits.delete(this.getKey(endpoint));
|
|
722
|
-
} else {
|
|
723
|
-
this.circuits.clear();
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
cleanup() {
|
|
727
|
-
const now = Date.now();
|
|
728
|
-
let cleaned = 0;
|
|
729
|
-
for (const [key, record] of this.circuits.entries()) {
|
|
730
|
-
if (record.state === "closed" && record.failures === 0 && record.lastFailureTime > 0 && now - record.lastFailureTime > this.staleThresholdMs) {
|
|
731
|
-
this.circuits.delete(key);
|
|
732
|
-
cleaned++;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
if (cleaned > 0) {
|
|
736
|
-
logInfo(`Circuit breaker cleanup: removed ${cleaned} stale circuits`);
|
|
737
|
-
}
|
|
738
|
-
return cleaned;
|
|
739
|
-
}
|
|
740
|
-
getKeyStrategy() {
|
|
741
|
-
return this.keyStrategy;
|
|
742
|
-
}
|
|
743
|
-
destroy() {
|
|
744
|
-
if (this.cleanupTimer) {
|
|
745
|
-
clearInterval(this.cleanupTimer);
|
|
746
|
-
this.cleanupTimer = null;
|
|
747
|
-
}
|
|
748
|
-
this.circuits.clear();
|
|
749
|
-
}
|
|
750
|
-
shutdown() {
|
|
751
|
-
this.destroy();
|
|
752
|
-
}
|
|
753
|
-
startCleanupTimer(intervalMs) {
|
|
754
|
-
this.cleanupTimer = setInterval(() => {
|
|
755
|
-
this.cleanup();
|
|
756
|
-
}, intervalMs);
|
|
757
|
-
this.cleanupTimer.unref();
|
|
758
|
-
}
|
|
718
|
+
"GET:wars/{war_id}/killmails": 3600
|
|
759
719
|
};
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
720
|
+
|
|
721
|
+
// src/core/requestPipeline/cachePolicy.ts
|
|
722
|
+
function lookupSpecTtl(method, templatePath) {
|
|
723
|
+
const normalized = templatePath.replace(/\/$/, "").replace(/\{(\w+)\}/g, (_, name) => `{${camelToSnake(name)}}`);
|
|
724
|
+
const key = `${method}:${normalized}`;
|
|
725
|
+
const seconds = esiCacheTtls[key];
|
|
726
|
+
return typeof seconds === "number" ? seconds * 1e3 : void 0;
|
|
727
|
+
}
|
|
728
|
+
function trySpecAwareCacheHit(client, url, method, templatePath, resolveCache2) {
|
|
729
|
+
if (method !== "GET" || !templatePath) return null;
|
|
730
|
+
const specTtlMs = lookupSpecTtl(method, templatePath);
|
|
731
|
+
if (!specTtlMs) return null;
|
|
732
|
+
const cache = resolveCache2(client);
|
|
733
|
+
if (!cache) return null;
|
|
734
|
+
const entry = cache.get(url);
|
|
735
|
+
if (!entry) return null;
|
|
736
|
+
const age = Date.now() - entry.timestamp;
|
|
737
|
+
if (age < specTtlMs) {
|
|
738
|
+
logDebug(
|
|
739
|
+
`Spec-aware cache hit for ${url} (age=${Math.round(age / 1e3)}s, ttl=${Math.round(specTtlMs / 1e3)}s)`
|
|
767
740
|
);
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
741
|
+
return {
|
|
742
|
+
headers: entry.headers,
|
|
743
|
+
body: entry.data,
|
|
744
|
+
status: 200,
|
|
745
|
+
fromCache: true,
|
|
746
|
+
cacheHitType: "spec-ttl"
|
|
747
|
+
};
|
|
772
748
|
}
|
|
773
|
-
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
function tryStaleCacheResponse(client, url, parsed, resolveCache2) {
|
|
752
|
+
const cache = resolveCache2(client);
|
|
753
|
+
if (!cache) return null;
|
|
754
|
+
const cachedEntry = cache.get(url);
|
|
755
|
+
if (!cachedEntry) return null;
|
|
756
|
+
return {
|
|
757
|
+
headers: { ...cachedEntry.headers, ...parsed.raw },
|
|
758
|
+
body: cachedEntry.data,
|
|
759
|
+
status: 200,
|
|
760
|
+
fromCache: true,
|
|
761
|
+
stale: true,
|
|
762
|
+
cacheHitType: "stale-on-error"
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function cacheResponse(client, url, method, endpoint, parsed, data, useETag, resolveCache2, templatePath) {
|
|
766
|
+
const cache = resolveCache2(client);
|
|
767
|
+
if (useETag && method === "GET" && cache && parsed.etag) {
|
|
768
|
+
const headerTtl = parseCacheControlTtl(parsed.raw);
|
|
769
|
+
const specTtlMs = templatePath ? lookupSpecTtl(method, templatePath) : void 0;
|
|
770
|
+
const ttl = specTtlMs ?? headerTtl;
|
|
771
|
+
cache.set(url, parsed.etag, data, parsed.raw, ttl);
|
|
772
|
+
const ttlInfo = ttl ? ` (ttl=${ttl}ms)` : "";
|
|
773
|
+
logDebug(`Cached response for ${url} with ETag ${parsed.etag}${ttlInfo}`);
|
|
774
|
+
}
|
|
775
|
+
if (method !== "GET" && cache) {
|
|
776
|
+
cache.deleteByPath(endpoint.split("?")[0]);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
774
779
|
|
|
775
780
|
// src/core/requestPipeline/statusHandling.ts
|
|
776
781
|
var STATUS_MESSAGES = {
|
|
@@ -791,11 +796,11 @@ var STATUS_MESSAGES = {
|
|
|
791
796
|
};
|
|
792
797
|
function handleEarlyStatus(client, status, url, parsed, useETag, resolveCache2) {
|
|
793
798
|
if (status === 201) {
|
|
794
|
-
return { headers: parsed.raw, body: void 0 };
|
|
799
|
+
return { headers: parsed.raw, body: void 0, status: 201 };
|
|
795
800
|
}
|
|
796
801
|
if (status === 204) {
|
|
797
802
|
logInfo(`No Content for endpoint: ${url}`);
|
|
798
|
-
return { headers: parsed.raw, body: void 0 };
|
|
803
|
+
return { headers: parsed.raw, body: void 0, status: 204 };
|
|
799
804
|
}
|
|
800
805
|
if (status === 304) {
|
|
801
806
|
const cache = resolveCache2(client);
|
|
@@ -806,6 +811,7 @@ function handleEarlyStatus(client, status, url, parsed, useETag, resolveCache2)
|
|
|
806
811
|
return {
|
|
807
812
|
headers: { ...cachedEntry.headers, ...parsed.raw },
|
|
808
813
|
body: cachedEntry.data,
|
|
814
|
+
status: 304,
|
|
809
815
|
fromCache: true,
|
|
810
816
|
cacheHitType: "etag-304"
|
|
811
817
|
};
|
|
@@ -831,7 +837,7 @@ function handleErrorResponse(client, response, url, parsed, useETag, resolveCach
|
|
|
831
837
|
);
|
|
832
838
|
if (staleResult) {
|
|
833
839
|
logWarn(`${errorMessage} for ${url} \u2014 serving stale cache`);
|
|
834
|
-
return staleResult;
|
|
840
|
+
return { ...staleResult, status: response.status };
|
|
835
841
|
}
|
|
836
842
|
}
|
|
837
843
|
if (response.status === 420 || response.status === 429) {
|
|
@@ -856,18 +862,102 @@ function wrapError(error) {
|
|
|
856
862
|
throw buildError(String(error), "ESIJS_ERROR");
|
|
857
863
|
}
|
|
858
864
|
|
|
865
|
+
// src/core/util/retry.ts
|
|
866
|
+
function retryDelay(attempt, baseMs, maxMs) {
|
|
867
|
+
const exponential = baseMs * Math.pow(2, attempt);
|
|
868
|
+
const jitter = exponential * (0.75 + Math.random() * 0.5);
|
|
869
|
+
return Math.min(jitter, maxMs);
|
|
870
|
+
}
|
|
871
|
+
|
|
859
872
|
// src/core/util/sleep.ts
|
|
860
873
|
function sleep(ms) {
|
|
861
874
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
862
875
|
}
|
|
863
876
|
|
|
877
|
+
// src/core/RetryStrategy.ts
|
|
878
|
+
var RetryStrategy = class {
|
|
879
|
+
maxRetries;
|
|
880
|
+
baseDelayMs;
|
|
881
|
+
maxDelayMs;
|
|
882
|
+
retryMutations;
|
|
883
|
+
constructor(config) {
|
|
884
|
+
this.maxRetries = config?.maxRetries ?? 0;
|
|
885
|
+
this.baseDelayMs = config?.baseDelayMs ?? 1e3;
|
|
886
|
+
this.maxDelayMs = config?.maxDelayMs ?? 3e4;
|
|
887
|
+
this.retryMutations = config?.retryMutations ?? false;
|
|
888
|
+
}
|
|
889
|
+
async execute(operation, context) {
|
|
890
|
+
const canRetryMethod = context.method === "GET" || this.retryMutations;
|
|
891
|
+
let lastError;
|
|
892
|
+
let refreshAttempted = false;
|
|
893
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
894
|
+
try {
|
|
895
|
+
return await operation();
|
|
896
|
+
} catch (error) {
|
|
897
|
+
if (error instanceof CircuitOpenError) {
|
|
898
|
+
throw error;
|
|
899
|
+
}
|
|
900
|
+
if (!refreshAttempted && error instanceof EsiError && error.statusCode === 401 && context.requiresAuth && context.refreshToken) {
|
|
901
|
+
refreshAttempted = true;
|
|
902
|
+
logInfo("Received 401, attempting token refresh...");
|
|
903
|
+
try {
|
|
904
|
+
await context.refreshToken();
|
|
905
|
+
} catch (refreshError) {
|
|
906
|
+
if (refreshError instanceof EsiError || refreshError instanceof CircuitOpenError) {
|
|
907
|
+
throw refreshError;
|
|
908
|
+
}
|
|
909
|
+
const msg = refreshError instanceof Error ? refreshError.message : String(refreshError);
|
|
910
|
+
logError(`Token refresh failed: ${msg}`);
|
|
911
|
+
throw buildError(
|
|
912
|
+
`Token refresh failed: ${msg}`,
|
|
913
|
+
"TOKEN_REFRESH_FAILED"
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
logInfo("Token refreshed, retrying request");
|
|
917
|
+
attempt--;
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (error instanceof EsiError && error.retryable && canRetryMethod && attempt < this.maxRetries) {
|
|
921
|
+
const delay = retryDelay(attempt, this.baseDelayMs, this.maxDelayMs);
|
|
922
|
+
logWarn(
|
|
923
|
+
`Request to ${context.endpoint} failed (${error.statusCode}), retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${this.maxRetries})`
|
|
924
|
+
);
|
|
925
|
+
await sleep(delay);
|
|
926
|
+
lastError = error;
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
throw lastError;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// src/core/requestPipeline/dependencies.ts
|
|
937
|
+
function resolveCache(client) {
|
|
938
|
+
return client.getCache();
|
|
939
|
+
}
|
|
940
|
+
function resolveRateLimiter(client) {
|
|
941
|
+
const limiter = client.getRateLimiter();
|
|
942
|
+
if (!limiter) {
|
|
943
|
+
throw buildError(
|
|
944
|
+
"No rate limiter configured on ApiClient. Set one via apiClient.setRateLimiter(new RateLimiter()).",
|
|
945
|
+
"CONFIGURATION_ERROR"
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return limiter;
|
|
949
|
+
}
|
|
950
|
+
function resolveCircuitBreaker(client) {
|
|
951
|
+
return client.getCircuitBreaker();
|
|
952
|
+
}
|
|
953
|
+
function resolveRetryStrategy(client) {
|
|
954
|
+
return client.getRetryStrategy() ?? new RetryStrategy(client.getRetryConfig() ?? void 0);
|
|
955
|
+
}
|
|
956
|
+
|
|
864
957
|
// src/core/pagination/PaginationHandler.ts
|
|
865
958
|
var PaginationHandler = class {
|
|
866
959
|
static DEFAULT_OPTIONS = {
|
|
867
960
|
maxPages: 1e3,
|
|
868
|
-
// Reasonable limit to prevent infinite loops
|
|
869
|
-
maxRetries: 3,
|
|
870
|
-
retryDelayMs: 1e3,
|
|
871
961
|
stopOnEmptyPage: true
|
|
872
962
|
};
|
|
873
963
|
/**
|
|
@@ -887,9 +977,11 @@ var PaginationHandler = class {
|
|
|
887
977
|
try {
|
|
888
978
|
if (rateLimiter) await rateLimiter.checkRateLimit(templatePath, method);
|
|
889
979
|
const pageData = await this.fetchPageWithRetry(
|
|
980
|
+
client,
|
|
890
981
|
endpoint,
|
|
982
|
+
method,
|
|
983
|
+
requiresAuth,
|
|
891
984
|
page,
|
|
892
|
-
opts,
|
|
893
985
|
pageFetch
|
|
894
986
|
);
|
|
895
987
|
if (opts.stopOnEmptyPage && (!pageData || pageData.length === 0)) {
|
|
@@ -913,36 +1005,35 @@ var PaginationHandler = class {
|
|
|
913
1005
|
return allData;
|
|
914
1006
|
}
|
|
915
1007
|
/**
|
|
916
|
-
* Fetch a single page with retry logic
|
|
1008
|
+
* Fetch a single page with retry logic.
|
|
1009
|
+
*
|
|
1010
|
+
* Delegates to the configured `IRetryStrategy` (exponential backoff
|
|
1011
|
+
* with jitter) instead of implementing a custom retry loop.
|
|
917
1012
|
*/
|
|
918
|
-
static async fetchPageWithRetry(endpoint,
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1013
|
+
static async fetchPageWithRetry(client, endpoint, method, requiresAuth, page, pageFetch) {
|
|
1014
|
+
const retryStrategy = resolveRetryStrategy(client);
|
|
1015
|
+
const paginatedEndpoint = this.buildPaginatedEndpoint(endpoint, page);
|
|
1016
|
+
return retryStrategy.execute(
|
|
1017
|
+
() => {
|
|
1018
|
+
logInfo(`Fetching page ${page} via pipeline: ${paginatedEndpoint}`);
|
|
1019
|
+
return pageFetch(paginatedEndpoint);
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
endpoint: paginatedEndpoint,
|
|
1023
|
+
method,
|
|
1024
|
+
requiresAuth,
|
|
1025
|
+
refreshToken: client.hasTokenProvider() ? () => client.refreshToken().then(() => {
|
|
1026
|
+
}) : void 0
|
|
931
1027
|
}
|
|
932
|
-
}
|
|
933
|
-
throw lastError || new Error(
|
|
934
|
-
`Failed to fetch page ${page} after ${options.maxRetries} attempts`
|
|
935
1028
|
);
|
|
936
1029
|
}
|
|
937
1030
|
/**
|
|
938
|
-
*
|
|
1031
|
+
* Build a paginated endpoint path.
|
|
939
1032
|
* Preserves any existing query params on the endpoint and appends page=N.
|
|
940
1033
|
*/
|
|
941
|
-
static
|
|
1034
|
+
static buildPaginatedEndpoint(endpoint, page) {
|
|
942
1035
|
const separator = endpoint.includes("?") ? "&" : "?";
|
|
943
|
-
|
|
944
|
-
logInfo(`Fetching page ${page} via pipeline: ${paginatedEndpoint}`);
|
|
945
|
-
return pageFetch(paginatedEndpoint);
|
|
1036
|
+
return `${endpoint}${separator}page=${page}`;
|
|
946
1037
|
}
|
|
947
1038
|
};
|
|
948
1039
|
|
|
@@ -953,12 +1044,12 @@ function handleCursorPagination(parsed, data) {
|
|
|
953
1044
|
before: parsed.cursorBefore,
|
|
954
1045
|
after: parsed.cursorAfter
|
|
955
1046
|
};
|
|
956
|
-
return { headers: parsed.raw, body: data, cursors };
|
|
1047
|
+
return { headers: parsed.raw, body: data, status: 200, cursors };
|
|
957
1048
|
}
|
|
958
1049
|
async function handleOffsetPagination(client, endpoint, method, requiresAuth, parsed, data, body, url, useETag, pageFetch, resolveCache2, templatePath) {
|
|
959
1050
|
const totalPages = parsed.xPages;
|
|
960
1051
|
if (totalPages <= 1) {
|
|
961
|
-
return { headers: parsed.raw, body: data };
|
|
1052
|
+
return { headers: parsed.raw, body: data, status: 200 };
|
|
962
1053
|
}
|
|
963
1054
|
logInfo(
|
|
964
1055
|
`Found ${totalPages} pages, fetching additional pages with rate limiting...`
|
|
@@ -975,8 +1066,7 @@ async function handleOffsetPagination(client, endpoint, method, requiresAuth, pa
|
|
|
975
1066
|
body,
|
|
976
1067
|
{
|
|
977
1068
|
maxPages: Math.min(totalPages, 1e3),
|
|
978
|
-
stopOnEmptyPage: true
|
|
979
|
-
maxRetries: 3
|
|
1069
|
+
stopOnEmptyPage: true
|
|
980
1070
|
},
|
|
981
1071
|
pageFetch,
|
|
982
1072
|
templatePath
|
|
@@ -992,7 +1082,7 @@ async function handleOffsetPagination(client, endpoint, method, requiresAuth, pa
|
|
|
992
1082
|
resolveCache2,
|
|
993
1083
|
templatePath
|
|
994
1084
|
);
|
|
995
|
-
return { headers: parsed.raw, body: allData };
|
|
1085
|
+
return { headers: parsed.raw, body: allData, status: 200 };
|
|
996
1086
|
} catch (paginationError) {
|
|
997
1087
|
const msg = paginationError instanceof Error ? paginationError.message : String(paginationError);
|
|
998
1088
|
logWarn(`Pagination failed for ${url}: ${msg}`);
|
|
@@ -1034,7 +1124,7 @@ async function applyResponseInterceptors(client, result, url, endpoint, method,
|
|
|
1034
1124
|
url,
|
|
1035
1125
|
endpoint,
|
|
1036
1126
|
method,
|
|
1037
|
-
status: 200,
|
|
1127
|
+
status: result.status ?? 200,
|
|
1038
1128
|
headers: result.headers,
|
|
1039
1129
|
body: result.body,
|
|
1040
1130
|
durationMs: Date.now() - startTime,
|
|
@@ -1196,93 +1286,6 @@ async function fetchOnePage(client, endpoint, method, body, requiresAuth, useETa
|
|
|
1196
1286
|
return { data, parsed, url };
|
|
1197
1287
|
}
|
|
1198
1288
|
|
|
1199
|
-
// src/core/util/retry.ts
|
|
1200
|
-
function retryDelay(attempt, baseMs, maxMs) {
|
|
1201
|
-
const exponential = baseMs * Math.pow(2, attempt);
|
|
1202
|
-
const jitter = exponential * (0.75 + Math.random() * 0.5);
|
|
1203
|
-
return Math.min(jitter, maxMs);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// src/core/RetryStrategy.ts
|
|
1207
|
-
var RetryStrategy = class {
|
|
1208
|
-
maxRetries;
|
|
1209
|
-
baseDelayMs;
|
|
1210
|
-
maxDelayMs;
|
|
1211
|
-
retryMutations;
|
|
1212
|
-
constructor(config) {
|
|
1213
|
-
this.maxRetries = config?.maxRetries ?? 0;
|
|
1214
|
-
this.baseDelayMs = config?.baseDelayMs ?? 1e3;
|
|
1215
|
-
this.maxDelayMs = config?.maxDelayMs ?? 3e4;
|
|
1216
|
-
this.retryMutations = config?.retryMutations ?? false;
|
|
1217
|
-
}
|
|
1218
|
-
async execute(operation, context) {
|
|
1219
|
-
const canRetryMethod = context.method === "GET" || this.retryMutations;
|
|
1220
|
-
let lastError;
|
|
1221
|
-
let refreshAttempted = false;
|
|
1222
|
-
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
1223
|
-
try {
|
|
1224
|
-
return await operation();
|
|
1225
|
-
} catch (error) {
|
|
1226
|
-
if (error instanceof CircuitOpenError) {
|
|
1227
|
-
throw error;
|
|
1228
|
-
}
|
|
1229
|
-
if (!refreshAttempted && error instanceof EsiError && error.statusCode === 401 && context.requiresAuth && context.refreshToken) {
|
|
1230
|
-
refreshAttempted = true;
|
|
1231
|
-
logInfo("Received 401, attempting token refresh...");
|
|
1232
|
-
try {
|
|
1233
|
-
await context.refreshToken();
|
|
1234
|
-
} catch (refreshError) {
|
|
1235
|
-
if (refreshError instanceof EsiError || refreshError instanceof CircuitOpenError) {
|
|
1236
|
-
throw refreshError;
|
|
1237
|
-
}
|
|
1238
|
-
const msg = refreshError instanceof Error ? refreshError.message : String(refreshError);
|
|
1239
|
-
logError(`Token refresh failed: ${msg}`);
|
|
1240
|
-
throw buildError(
|
|
1241
|
-
`Token refresh failed: ${msg}`,
|
|
1242
|
-
"TOKEN_REFRESH_FAILED"
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
logInfo("Token refreshed, retrying request");
|
|
1246
|
-
attempt--;
|
|
1247
|
-
continue;
|
|
1248
|
-
}
|
|
1249
|
-
if (error instanceof EsiError && error.retryable && canRetryMethod && attempt < this.maxRetries) {
|
|
1250
|
-
const delay = retryDelay(attempt, this.baseDelayMs, this.maxDelayMs);
|
|
1251
|
-
logWarn(
|
|
1252
|
-
`Request to ${context.endpoint} failed (${error.statusCode}), retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${this.maxRetries})`
|
|
1253
|
-
);
|
|
1254
|
-
await sleep(delay);
|
|
1255
|
-
lastError = error;
|
|
1256
|
-
continue;
|
|
1257
|
-
}
|
|
1258
|
-
throw error;
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
throw lastError;
|
|
1262
|
-
}
|
|
1263
|
-
};
|
|
1264
|
-
|
|
1265
|
-
// src/core/requestPipeline/dependencies.ts
|
|
1266
|
-
function resolveCache(client) {
|
|
1267
|
-
return client.getCache();
|
|
1268
|
-
}
|
|
1269
|
-
function resolveRateLimiter(client) {
|
|
1270
|
-
const limiter = client.getRateLimiter();
|
|
1271
|
-
if (!limiter) {
|
|
1272
|
-
throw buildError(
|
|
1273
|
-
"No rate limiter configured on ApiClient. Set one via apiClient.setRateLimiter(new RateLimiter()).",
|
|
1274
|
-
"CONFIGURATION_ERROR"
|
|
1275
|
-
);
|
|
1276
|
-
}
|
|
1277
|
-
return limiter;
|
|
1278
|
-
}
|
|
1279
|
-
function resolveCircuitBreaker(client) {
|
|
1280
|
-
return client.getCircuitBreaker();
|
|
1281
|
-
}
|
|
1282
|
-
function resolveRetryStrategy(client) {
|
|
1283
|
-
return client.getRetryStrategy() ?? new RetryStrategy(client.getRetryConfig() ?? void 0);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
1289
|
// src/core/ApiRequestHandler.ts
|
|
1287
1290
|
var executeRequest = async (client, endpoint, method, body, requiresAuth = false, useETag = true, requestTimeout, templatePath) => {
|
|
1288
1291
|
const startTime = Date.now();
|
|
@@ -1319,7 +1322,7 @@ var executeRequest = async (client, endpoint, method, body, requiresAuth = false
|
|
|
1319
1322
|
} catch {
|
|
1320
1323
|
data2 = void 0;
|
|
1321
1324
|
}
|
|
1322
|
-
return finish({ headers: parsed.raw, body: data2 });
|
|
1325
|
+
return finish({ headers: parsed.raw, body: data2, status: 201 });
|
|
1323
1326
|
}
|
|
1324
1327
|
const earlyResult = handleEarlyStatus(
|
|
1325
1328
|
client,
|
|
@@ -8885,6 +8888,12 @@ function configureApiClient(client, config) {
|
|
|
8885
8888
|
client.setRetryConfig(config.retryConfig);
|
|
8886
8889
|
} else if (config?.retryAttempts !== void 0) {
|
|
8887
8890
|
client.setRetryConfig({ maxRetries: config.retryAttempts });
|
|
8891
|
+
} else {
|
|
8892
|
+
client.setRetryConfig({
|
|
8893
|
+
maxRetries: 3,
|
|
8894
|
+
baseDelayMs: 1e3,
|
|
8895
|
+
maxDelayMs: 3e4
|
|
8896
|
+
});
|
|
8888
8897
|
}
|
|
8889
8898
|
if (config?.retryStrategy) {
|
|
8890
8899
|
client.setRetryStrategy(config.retryStrategy);
|
|
@@ -9808,6 +9817,7 @@ export {
|
|
|
9808
9817
|
fetchAllCursorPages,
|
|
9809
9818
|
fetchPages,
|
|
9810
9819
|
getDefaultClient,
|
|
9820
|
+
isCircuitOpen,
|
|
9811
9821
|
isEsiError,
|
|
9812
9822
|
isForbidden,
|
|
9813
9823
|
isNotFound,
|