@djvlc/openapi-client-core 1.2.0 → 1.2.1
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/README.md +996 -146
- package/dist/index.d.mts +2635 -191
- package/dist/index.d.ts +2635 -191
- package/dist/index.js +2764 -338
- package/dist/index.mjs +2681 -333
- package/package.json +1 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -11697,44 +11697,2103 @@ var require_follow_redirects = __commonJS({
|
|
|
11697
11697
|
}
|
|
11698
11698
|
});
|
|
11699
11699
|
|
|
11700
|
-
// src/
|
|
11701
|
-
var
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11700
|
+
// src/types/retry.ts
|
|
11701
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
11702
|
+
maxRetries: 3,
|
|
11703
|
+
initialDelayMs: 1e3,
|
|
11704
|
+
maxDelayMs: 3e4,
|
|
11705
|
+
backoffStrategy: "exponential",
|
|
11706
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
11707
|
+
retryOnNetworkError: true,
|
|
11708
|
+
retryOnTimeout: true,
|
|
11709
|
+
respectRetryAfter: true,
|
|
11710
|
+
jitterFactor: 0.1
|
|
11711
|
+
};
|
|
11712
|
+
|
|
11713
|
+
// src/types/logger.ts
|
|
11714
|
+
var LOG_LEVEL_PRIORITY = {
|
|
11715
|
+
debug: 0,
|
|
11716
|
+
info: 1,
|
|
11717
|
+
warn: 2,
|
|
11718
|
+
error: 3
|
|
11719
|
+
};
|
|
11720
|
+
|
|
11721
|
+
// src/errors/base-error.ts
|
|
11722
|
+
var BaseClientError = class extends Error {
|
|
11723
|
+
constructor(message) {
|
|
11724
|
+
super(message);
|
|
11725
|
+
this.name = this.constructor.name;
|
|
11726
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
11727
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11728
|
+
if (Error.captureStackTrace) {
|
|
11729
|
+
Error.captureStackTrace(this, this.constructor);
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11732
|
+
/**
|
|
11733
|
+
* 转换为 JSON 对象(用于日志和序列化)
|
|
11734
|
+
*/
|
|
11735
|
+
toJSON() {
|
|
11736
|
+
return {
|
|
11737
|
+
name: this.name,
|
|
11738
|
+
type: this.type,
|
|
11739
|
+
message: this.message,
|
|
11740
|
+
retryable: this.retryable,
|
|
11741
|
+
timestamp: this.timestamp.toISOString(),
|
|
11742
|
+
stack: this.stack
|
|
11743
|
+
};
|
|
11744
|
+
}
|
|
11745
|
+
/**
|
|
11746
|
+
* 转换为字符串
|
|
11747
|
+
*/
|
|
11748
|
+
toString() {
|
|
11749
|
+
return `${this.name}: ${this.message}`;
|
|
11750
|
+
}
|
|
11751
|
+
};
|
|
11752
|
+
|
|
11753
|
+
// src/errors/api-error.ts
|
|
11754
|
+
var ApiError = class _ApiError extends BaseClientError {
|
|
11755
|
+
constructor(config) {
|
|
11756
|
+
super(config.message);
|
|
11757
|
+
this.type = "API_ERROR";
|
|
11758
|
+
this.code = config.code;
|
|
11759
|
+
this.statusCode = config.statusCode;
|
|
11760
|
+
this.details = config.details;
|
|
11761
|
+
this.requestId = config.requestId;
|
|
11762
|
+
this.traceId = config.traceId;
|
|
11763
|
+
this.retryAfterSeconds = config.retryAfter;
|
|
11764
|
+
this.rawResponse = config.rawResponse;
|
|
11765
|
+
}
|
|
11766
|
+
/**
|
|
11767
|
+
* 是否可重试
|
|
11768
|
+
*
|
|
11769
|
+
* 5xx 和 429 错误通常可重试
|
|
11770
|
+
*/
|
|
11771
|
+
get retryable() {
|
|
11772
|
+
if (this.statusCode === 429) {
|
|
11773
|
+
return true;
|
|
11774
|
+
}
|
|
11775
|
+
if (this.statusCode >= 500 && this.statusCode < 600 && this.statusCode !== 501) {
|
|
11776
|
+
return true;
|
|
11777
|
+
}
|
|
11778
|
+
return false;
|
|
11779
|
+
}
|
|
11780
|
+
// ============================================================
|
|
11781
|
+
// 状态码判断方法
|
|
11782
|
+
// ============================================================
|
|
11783
|
+
/**
|
|
11784
|
+
* 是否为未授权错误 (401)
|
|
11785
|
+
*/
|
|
11786
|
+
isUnauthorized() {
|
|
11787
|
+
return this.statusCode === 401;
|
|
11788
|
+
}
|
|
11789
|
+
/**
|
|
11790
|
+
* 是否为禁止访问错误 (403)
|
|
11791
|
+
*/
|
|
11792
|
+
isForbidden() {
|
|
11793
|
+
return this.statusCode === 403;
|
|
11794
|
+
}
|
|
11795
|
+
/**
|
|
11796
|
+
* 是否为认证相关错误 (401/403)
|
|
11797
|
+
*/
|
|
11798
|
+
isAuthError() {
|
|
11799
|
+
return this.isUnauthorized() || this.isForbidden();
|
|
11800
|
+
}
|
|
11801
|
+
/**
|
|
11802
|
+
* 是否为未找到错误 (404)
|
|
11803
|
+
*/
|
|
11804
|
+
isNotFound() {
|
|
11805
|
+
return this.statusCode === 404;
|
|
11806
|
+
}
|
|
11807
|
+
/**
|
|
11808
|
+
* 是否为冲突错误 (409)
|
|
11809
|
+
*/
|
|
11810
|
+
isConflict() {
|
|
11811
|
+
return this.statusCode === 409;
|
|
11812
|
+
}
|
|
11813
|
+
/**
|
|
11814
|
+
* 是否为验证错误 (400/422)
|
|
11815
|
+
*/
|
|
11816
|
+
isValidationError() {
|
|
11817
|
+
return this.statusCode === 400 || this.statusCode === 422;
|
|
11818
|
+
}
|
|
11819
|
+
/**
|
|
11820
|
+
* 是否为限流错误 (429)
|
|
11821
|
+
*/
|
|
11822
|
+
isRateLimited() {
|
|
11823
|
+
return this.statusCode === 429;
|
|
11824
|
+
}
|
|
11825
|
+
/**
|
|
11826
|
+
* 是否为客户端错误 (4xx)
|
|
11827
|
+
*/
|
|
11828
|
+
isClientError() {
|
|
11829
|
+
return this.statusCode >= 400 && this.statusCode < 500;
|
|
11830
|
+
}
|
|
11831
|
+
/**
|
|
11832
|
+
* 是否为服务端错误 (5xx)
|
|
11833
|
+
*/
|
|
11834
|
+
isServerError() {
|
|
11835
|
+
return this.statusCode >= 500 && this.statusCode < 600;
|
|
11836
|
+
}
|
|
11837
|
+
// ============================================================
|
|
11838
|
+
// 重试相关方法
|
|
11839
|
+
// ============================================================
|
|
11840
|
+
/**
|
|
11841
|
+
* 获取 Retry-After 值(秒)
|
|
11842
|
+
*/
|
|
11843
|
+
getRetryAfter() {
|
|
11844
|
+
return this.retryAfterSeconds;
|
|
11845
|
+
}
|
|
11846
|
+
/**
|
|
11847
|
+
* 获取推荐的重试延迟(毫秒)
|
|
11848
|
+
*
|
|
11849
|
+
* @param defaultMs - 默认延迟(毫秒)
|
|
11850
|
+
*/
|
|
11851
|
+
getRetryDelayMs(defaultMs = 1e3) {
|
|
11852
|
+
if (this.retryAfterSeconds !== void 0) {
|
|
11853
|
+
return this.retryAfterSeconds * 1e3;
|
|
11854
|
+
}
|
|
11855
|
+
return defaultMs;
|
|
11856
|
+
}
|
|
11857
|
+
// ============================================================
|
|
11858
|
+
// 序列化
|
|
11859
|
+
// ============================================================
|
|
11860
|
+
toJSON() {
|
|
11861
|
+
return {
|
|
11862
|
+
...super.toJSON(),
|
|
11863
|
+
code: this.code,
|
|
11864
|
+
statusCode: this.statusCode,
|
|
11865
|
+
details: this.details,
|
|
11866
|
+
requestId: this.requestId,
|
|
11867
|
+
traceId: this.traceId,
|
|
11868
|
+
retryAfterSeconds: this.retryAfterSeconds
|
|
11869
|
+
};
|
|
11870
|
+
}
|
|
11871
|
+
// ============================================================
|
|
11872
|
+
// 静态方法
|
|
11873
|
+
// ============================================================
|
|
11874
|
+
/**
|
|
11875
|
+
* 类型守卫:判断是否为 ApiError
|
|
11876
|
+
*/
|
|
11877
|
+
static is(error) {
|
|
11878
|
+
return error instanceof _ApiError;
|
|
11879
|
+
}
|
|
11880
|
+
/**
|
|
11881
|
+
* 从响应数据创建 ApiError
|
|
11882
|
+
*/
|
|
11883
|
+
static fromResponse(data, statusCode, retryAfter) {
|
|
11884
|
+
return new _ApiError({
|
|
11885
|
+
message: data.message ?? `HTTP Error ${statusCode}`,
|
|
11886
|
+
code: data.code ?? `HTTP_${statusCode}`,
|
|
11887
|
+
statusCode,
|
|
11888
|
+
details: data.details,
|
|
11889
|
+
requestId: data.requestId ?? "unknown",
|
|
11890
|
+
traceId: data.traceId,
|
|
11891
|
+
retryAfter,
|
|
11892
|
+
rawResponse: data
|
|
11893
|
+
});
|
|
11894
|
+
}
|
|
11895
|
+
};
|
|
11896
|
+
|
|
11897
|
+
// src/errors/network-error.ts
|
|
11898
|
+
var NetworkError = class _NetworkError extends BaseClientError {
|
|
11899
|
+
constructor(message, cause) {
|
|
11900
|
+
super(message);
|
|
11901
|
+
this.type = "NETWORK_ERROR";
|
|
11902
|
+
this.retryable = true;
|
|
11903
|
+
this.cause = cause;
|
|
11904
|
+
}
|
|
11905
|
+
toJSON() {
|
|
11906
|
+
return {
|
|
11907
|
+
...super.toJSON(),
|
|
11908
|
+
cause: this.cause ? {
|
|
11909
|
+
name: this.cause.name,
|
|
11910
|
+
message: this.cause.message
|
|
11911
|
+
} : void 0
|
|
11912
|
+
};
|
|
11913
|
+
}
|
|
11914
|
+
/**
|
|
11915
|
+
* 类型守卫:判断是否为 NetworkError
|
|
11916
|
+
*/
|
|
11917
|
+
static is(error) {
|
|
11918
|
+
return error instanceof _NetworkError;
|
|
11919
|
+
}
|
|
11920
|
+
/**
|
|
11921
|
+
* 从原生 fetch 错误创建 NetworkError
|
|
11922
|
+
*/
|
|
11923
|
+
static fromFetchError(error) {
|
|
11924
|
+
const message = error.message.toLowerCase();
|
|
11925
|
+
if (message.includes("network") || message.includes("failed to fetch")) {
|
|
11926
|
+
return new _NetworkError("Network request failed", error);
|
|
11927
|
+
}
|
|
11928
|
+
if (message.includes("dns") || message.includes("getaddrinfo")) {
|
|
11929
|
+
return new _NetworkError("DNS resolution failed", error);
|
|
11930
|
+
}
|
|
11931
|
+
if (message.includes("connection refused") || message.includes("econnrefused")) {
|
|
11932
|
+
return new _NetworkError("Connection refused", error);
|
|
11933
|
+
}
|
|
11934
|
+
if (message.includes("connection reset") || message.includes("econnreset")) {
|
|
11935
|
+
return new _NetworkError("Connection reset", error);
|
|
11936
|
+
}
|
|
11937
|
+
if (message.includes("ssl") || message.includes("certificate")) {
|
|
11938
|
+
return new _NetworkError("SSL/TLS error", error);
|
|
11939
|
+
}
|
|
11940
|
+
return new _NetworkError(error.message || "Unknown network error", error);
|
|
11941
|
+
}
|
|
11942
|
+
};
|
|
11943
|
+
|
|
11944
|
+
// src/errors/timeout-error.ts
|
|
11945
|
+
var TimeoutError = class _TimeoutError extends BaseClientError {
|
|
11946
|
+
constructor(timeoutMs) {
|
|
11947
|
+
super(`Request timeout after ${timeoutMs}ms`);
|
|
11948
|
+
this.type = "TIMEOUT_ERROR";
|
|
11949
|
+
this.retryable = true;
|
|
11950
|
+
this.timeoutMs = timeoutMs;
|
|
11951
|
+
}
|
|
11952
|
+
toJSON() {
|
|
11953
|
+
return {
|
|
11954
|
+
...super.toJSON(),
|
|
11955
|
+
timeoutMs: this.timeoutMs
|
|
11956
|
+
};
|
|
11957
|
+
}
|
|
11958
|
+
/**
|
|
11959
|
+
* 类型守卫:判断是否为 TimeoutError
|
|
11960
|
+
*/
|
|
11961
|
+
static is(error) {
|
|
11962
|
+
return error instanceof _TimeoutError;
|
|
11963
|
+
}
|
|
11964
|
+
};
|
|
11965
|
+
|
|
11966
|
+
// src/errors/abort-error.ts
|
|
11967
|
+
var AbortError = class _AbortError extends BaseClientError {
|
|
11968
|
+
constructor(reason) {
|
|
11969
|
+
super(reason ?? "Request was aborted");
|
|
11970
|
+
this.type = "ABORT_ERROR";
|
|
11971
|
+
this.retryable = false;
|
|
11972
|
+
this.reason = reason;
|
|
11973
|
+
}
|
|
11974
|
+
toJSON() {
|
|
11975
|
+
return {
|
|
11976
|
+
...super.toJSON(),
|
|
11977
|
+
reason: this.reason
|
|
11978
|
+
};
|
|
11979
|
+
}
|
|
11980
|
+
/**
|
|
11981
|
+
* 类型守卫:判断是否为 AbortError
|
|
11982
|
+
*/
|
|
11983
|
+
static is(error) {
|
|
11984
|
+
return error instanceof _AbortError;
|
|
11985
|
+
}
|
|
11986
|
+
/**
|
|
11987
|
+
* 从原生 AbortError 创建
|
|
11988
|
+
*/
|
|
11989
|
+
static fromNative(error) {
|
|
11990
|
+
return new _AbortError(error.message);
|
|
11991
|
+
}
|
|
11992
|
+
};
|
|
11993
|
+
|
|
11994
|
+
// src/errors/index.ts
|
|
11995
|
+
function isRetryableError(error) {
|
|
11996
|
+
if (error instanceof ApiError || error instanceof NetworkError || error instanceof TimeoutError) {
|
|
11997
|
+
return error.retryable;
|
|
11998
|
+
}
|
|
11999
|
+
if (error instanceof AbortError) {
|
|
12000
|
+
return false;
|
|
12001
|
+
}
|
|
12002
|
+
return false;
|
|
12003
|
+
}
|
|
12004
|
+
function getRetryDelay(error, defaultMs = 1e3) {
|
|
12005
|
+
if (error instanceof ApiError) {
|
|
12006
|
+
return error.getRetryDelayMs(defaultMs);
|
|
12007
|
+
}
|
|
12008
|
+
return defaultMs;
|
|
12009
|
+
}
|
|
12010
|
+
function isClientError(error) {
|
|
12011
|
+
return error instanceof ApiError || error instanceof NetworkError || error instanceof TimeoutError || error instanceof AbortError;
|
|
12012
|
+
}
|
|
12013
|
+
function getErrorType(error) {
|
|
12014
|
+
if (error instanceof ApiError) {
|
|
12015
|
+
return error.type;
|
|
12016
|
+
}
|
|
12017
|
+
if (error instanceof NetworkError) {
|
|
12018
|
+
return error.type;
|
|
12019
|
+
}
|
|
12020
|
+
if (error instanceof TimeoutError) {
|
|
12021
|
+
return error.type;
|
|
12022
|
+
}
|
|
12023
|
+
if (error instanceof AbortError) {
|
|
12024
|
+
return error.type;
|
|
12025
|
+
}
|
|
12026
|
+
if (error instanceof Error) {
|
|
12027
|
+
return error.name;
|
|
12028
|
+
}
|
|
12029
|
+
return "UNKNOWN_ERROR";
|
|
12030
|
+
}
|
|
12031
|
+
|
|
12032
|
+
// src/auth/bearer-authenticator.ts
|
|
12033
|
+
var BearerAuthenticator = class {
|
|
12034
|
+
constructor(config) {
|
|
12035
|
+
this.type = "bearer";
|
|
12036
|
+
this.getToken = config.getToken;
|
|
12037
|
+
this.headerName = config.headerName ?? "Authorization";
|
|
12038
|
+
this.prefix = config.prefix ?? "Bearer";
|
|
12039
|
+
}
|
|
12040
|
+
async authenticate(headers) {
|
|
12041
|
+
const token = await this.getToken();
|
|
12042
|
+
if (token !== null && token !== "") {
|
|
12043
|
+
headers[this.headerName] = `${this.prefix} ${token}`;
|
|
12044
|
+
}
|
|
12045
|
+
}
|
|
12046
|
+
};
|
|
12047
|
+
function createBearerAuthenticator(config) {
|
|
12048
|
+
return new BearerAuthenticator(config);
|
|
12049
|
+
}
|
|
12050
|
+
|
|
12051
|
+
// src/auth/api-key-authenticator.ts
|
|
12052
|
+
var ApiKeyAuthenticator = class {
|
|
12053
|
+
constructor(config) {
|
|
12054
|
+
this.type = "api-key";
|
|
12055
|
+
this.apiKey = config.apiKey;
|
|
12056
|
+
this.headerName = config.headerName ?? "X-API-Key";
|
|
12057
|
+
}
|
|
12058
|
+
authenticate(headers) {
|
|
12059
|
+
headers[this.headerName] = this.apiKey;
|
|
12060
|
+
}
|
|
12061
|
+
};
|
|
12062
|
+
function createApiKeyAuthenticator(config) {
|
|
12063
|
+
return new ApiKeyAuthenticator(config);
|
|
12064
|
+
}
|
|
12065
|
+
|
|
12066
|
+
// src/auth/basic-authenticator.ts
|
|
12067
|
+
function encodeBase64(str) {
|
|
12068
|
+
if (typeof btoa !== "undefined") {
|
|
12069
|
+
const utf8Bytes = new TextEncoder().encode(str);
|
|
12070
|
+
const binaryString = Array.from(utf8Bytes, (byte) => String.fromCharCode(byte)).join("");
|
|
12071
|
+
return btoa(binaryString);
|
|
12072
|
+
}
|
|
12073
|
+
if (typeof Buffer !== "undefined") {
|
|
12074
|
+
return Buffer.from(str, "utf-8").toString("base64");
|
|
12075
|
+
}
|
|
12076
|
+
throw new Error("Base64 encoding is not supported in this environment");
|
|
12077
|
+
}
|
|
12078
|
+
var BasicAuthenticator = class {
|
|
12079
|
+
constructor(config) {
|
|
12080
|
+
this.type = "basic";
|
|
12081
|
+
this.encodedCredentials = encodeBase64(`${config.username}:${config.password}`);
|
|
12082
|
+
}
|
|
12083
|
+
authenticate(headers) {
|
|
12084
|
+
headers["Authorization"] = `Basic ${this.encodedCredentials}`;
|
|
12085
|
+
}
|
|
12086
|
+
};
|
|
12087
|
+
function createBasicAuthenticator(config) {
|
|
12088
|
+
return new BasicAuthenticator(config);
|
|
12089
|
+
}
|
|
12090
|
+
|
|
12091
|
+
// src/auth/custom-authenticator.ts
|
|
12092
|
+
var CustomAuthenticator = class {
|
|
12093
|
+
constructor(config) {
|
|
12094
|
+
this.type = "custom";
|
|
12095
|
+
this.authenticateFn = config.authenticate;
|
|
12096
|
+
}
|
|
12097
|
+
async authenticate(headers) {
|
|
12098
|
+
await this.authenticateFn(headers);
|
|
12099
|
+
}
|
|
12100
|
+
};
|
|
12101
|
+
function createCustomAuthenticator(config) {
|
|
12102
|
+
return new CustomAuthenticator(config);
|
|
12103
|
+
}
|
|
12104
|
+
|
|
12105
|
+
// src/auth/no-authenticator.ts
|
|
12106
|
+
var NoAuthenticator = class {
|
|
12107
|
+
constructor() {
|
|
12108
|
+
this.type = "none";
|
|
12109
|
+
}
|
|
12110
|
+
authenticate(_headers) {
|
|
12111
|
+
}
|
|
12112
|
+
};
|
|
12113
|
+
function createNoAuthenticator() {
|
|
12114
|
+
return new NoAuthenticator();
|
|
12115
|
+
}
|
|
12116
|
+
var noAuthenticator = new NoAuthenticator();
|
|
12117
|
+
|
|
12118
|
+
// src/auth/index.ts
|
|
12119
|
+
function createAuthenticatorFromConfig(config) {
|
|
12120
|
+
switch (config.type) {
|
|
12121
|
+
case "bearer":
|
|
12122
|
+
return new BearerAuthenticator({ getToken: config.getToken, headerName: config.headerName, prefix: config.prefix });
|
|
12123
|
+
case "api-key":
|
|
12124
|
+
return new ApiKeyAuthenticator({ apiKey: config.apiKey, headerName: config.headerName });
|
|
12125
|
+
case "basic":
|
|
12126
|
+
return new BasicAuthenticator({ username: config.username, password: config.password });
|
|
12127
|
+
case "custom":
|
|
12128
|
+
return new CustomAuthenticator({ authenticate: config.authenticate });
|
|
12129
|
+
case "none":
|
|
12130
|
+
return noAuthenticator;
|
|
12131
|
+
default: {
|
|
12132
|
+
const _exhaustiveCheck = config;
|
|
12133
|
+
throw new Error(`Unknown auth type: ${_exhaustiveCheck.type}`);
|
|
12134
|
+
}
|
|
12135
|
+
}
|
|
12136
|
+
}
|
|
12137
|
+
|
|
12138
|
+
// src/interceptors/manager.ts
|
|
12139
|
+
var InterceptorManager = class {
|
|
12140
|
+
constructor() {
|
|
12141
|
+
this.requestInterceptors = [];
|
|
12142
|
+
this.responseInterceptors = [];
|
|
12143
|
+
this.errorInterceptors = [];
|
|
12144
|
+
}
|
|
12145
|
+
/**
|
|
12146
|
+
* 获取所有拦截器
|
|
12147
|
+
*/
|
|
12148
|
+
get interceptors() {
|
|
12149
|
+
return {
|
|
12150
|
+
request: [...this.requestInterceptors],
|
|
12151
|
+
response: [...this.responseInterceptors],
|
|
12152
|
+
error: [...this.errorInterceptors]
|
|
12153
|
+
};
|
|
12154
|
+
}
|
|
12155
|
+
// ============================================================
|
|
12156
|
+
// 注册方法
|
|
12157
|
+
// ============================================================
|
|
12158
|
+
/**
|
|
12159
|
+
* 添加请求拦截器
|
|
12160
|
+
*
|
|
12161
|
+
* @param interceptor - 请求拦截器
|
|
12162
|
+
* @returns 返回自身,支持链式调用
|
|
12163
|
+
*/
|
|
12164
|
+
addRequestInterceptor(interceptor) {
|
|
12165
|
+
this.requestInterceptors.push(interceptor);
|
|
12166
|
+
this.sortInterceptors(this.requestInterceptors);
|
|
12167
|
+
return this;
|
|
12168
|
+
}
|
|
12169
|
+
/**
|
|
12170
|
+
* 添加响应拦截器
|
|
12171
|
+
*
|
|
12172
|
+
* @param interceptor - 响应拦截器
|
|
12173
|
+
* @returns 返回自身,支持链式调用
|
|
12174
|
+
*/
|
|
12175
|
+
addResponseInterceptor(interceptor) {
|
|
12176
|
+
this.responseInterceptors.push(interceptor);
|
|
12177
|
+
this.sortInterceptors(this.responseInterceptors);
|
|
12178
|
+
return this;
|
|
12179
|
+
}
|
|
12180
|
+
/**
|
|
12181
|
+
* 添加错误拦截器
|
|
12182
|
+
*
|
|
12183
|
+
* @param interceptor - 错误拦截器
|
|
12184
|
+
* @returns 返回自身,支持链式调用
|
|
12185
|
+
*/
|
|
12186
|
+
addErrorInterceptor(interceptor) {
|
|
12187
|
+
this.errorInterceptors.push(interceptor);
|
|
12188
|
+
this.sortInterceptors(this.errorInterceptors);
|
|
12189
|
+
return this;
|
|
12190
|
+
}
|
|
12191
|
+
/**
|
|
12192
|
+
* 批量添加拦截器
|
|
12193
|
+
*
|
|
12194
|
+
* @param interceptors - 拦截器配置
|
|
12195
|
+
* @returns 返回自身,支持链式调用
|
|
12196
|
+
*/
|
|
12197
|
+
addInterceptors(interceptors) {
|
|
12198
|
+
if (interceptors.request) {
|
|
12199
|
+
for (const i of interceptors.request) {
|
|
12200
|
+
this.addRequestInterceptor(i);
|
|
12201
|
+
}
|
|
12202
|
+
}
|
|
12203
|
+
if (interceptors.response) {
|
|
12204
|
+
for (const i of interceptors.response) {
|
|
12205
|
+
this.addResponseInterceptor(i);
|
|
12206
|
+
}
|
|
12207
|
+
}
|
|
12208
|
+
if (interceptors.error) {
|
|
12209
|
+
for (const i of interceptors.error) {
|
|
12210
|
+
this.addErrorInterceptor(i);
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
return this;
|
|
12214
|
+
}
|
|
12215
|
+
// ============================================================
|
|
12216
|
+
// 移除方法
|
|
12217
|
+
// ============================================================
|
|
12218
|
+
/**
|
|
12219
|
+
* 移除请求拦截器
|
|
12220
|
+
*
|
|
12221
|
+
* @param name - 拦截器名称
|
|
12222
|
+
* @returns 是否移除成功
|
|
12223
|
+
*/
|
|
12224
|
+
removeRequestInterceptor(name) {
|
|
12225
|
+
return this.removeByName(this.requestInterceptors, name);
|
|
12226
|
+
}
|
|
12227
|
+
/**
|
|
12228
|
+
* 移除响应拦截器
|
|
12229
|
+
*
|
|
12230
|
+
* @param name - 拦截器名称
|
|
12231
|
+
* @returns 是否移除成功
|
|
12232
|
+
*/
|
|
12233
|
+
removeResponseInterceptor(name) {
|
|
12234
|
+
return this.removeByName(this.responseInterceptors, name);
|
|
12235
|
+
}
|
|
12236
|
+
/**
|
|
12237
|
+
* 移除错误拦截器
|
|
12238
|
+
*
|
|
12239
|
+
* @param name - 拦截器名称
|
|
12240
|
+
* @returns 是否移除成功
|
|
12241
|
+
*/
|
|
12242
|
+
removeErrorInterceptor(name) {
|
|
12243
|
+
return this.removeByName(this.errorInterceptors, name);
|
|
12244
|
+
}
|
|
12245
|
+
/**
|
|
12246
|
+
* 清除所有拦截器
|
|
12247
|
+
*/
|
|
12248
|
+
clear() {
|
|
12249
|
+
this.requestInterceptors.length = 0;
|
|
12250
|
+
this.responseInterceptors.length = 0;
|
|
12251
|
+
this.errorInterceptors.length = 0;
|
|
12252
|
+
}
|
|
12253
|
+
// ============================================================
|
|
12254
|
+
// 执行方法
|
|
12255
|
+
// ============================================================
|
|
12256
|
+
/**
|
|
12257
|
+
* 执行请求拦截器链
|
|
12258
|
+
*
|
|
12259
|
+
* @param context - 请求上下文
|
|
12260
|
+
* @returns 修改后的请求选项
|
|
12261
|
+
*/
|
|
12262
|
+
async executeRequestInterceptors(context) {
|
|
12263
|
+
let options = context.options;
|
|
12264
|
+
for (const interceptor of this.requestInterceptors) {
|
|
12265
|
+
options = await interceptor.intercept({
|
|
12266
|
+
...context,
|
|
12267
|
+
options
|
|
12268
|
+
});
|
|
12269
|
+
}
|
|
12270
|
+
return options;
|
|
12271
|
+
}
|
|
12272
|
+
/**
|
|
12273
|
+
* 执行响应拦截器链
|
|
12274
|
+
*
|
|
12275
|
+
* @param response - 响应数据
|
|
12276
|
+
* @param context - 请求上下文
|
|
12277
|
+
* @returns 修改后的响应数据
|
|
12278
|
+
*/
|
|
12279
|
+
async executeResponseInterceptors(response, context) {
|
|
12280
|
+
let result = response;
|
|
12281
|
+
for (const interceptor of this.responseInterceptors) {
|
|
12282
|
+
result = await interceptor.intercept(result, context);
|
|
12283
|
+
}
|
|
12284
|
+
return result;
|
|
12285
|
+
}
|
|
12286
|
+
/**
|
|
12287
|
+
* 执行错误拦截器链
|
|
12288
|
+
*
|
|
12289
|
+
* @param error - 错误对象
|
|
12290
|
+
* @param context - 请求上下文
|
|
12291
|
+
* @returns 如果返回 ResponseData,则表示错误被恢复;否则错误继续传播
|
|
12292
|
+
* @throws 如果拦截器抛出新错误,则使用新错误
|
|
12293
|
+
*/
|
|
12294
|
+
async executeErrorInterceptors(error, context) {
|
|
12295
|
+
for (const interceptor of this.errorInterceptors) {
|
|
12296
|
+
const result = await interceptor.intercept(error, context);
|
|
12297
|
+
if (result !== void 0) {
|
|
12298
|
+
return result;
|
|
12299
|
+
}
|
|
12300
|
+
}
|
|
12301
|
+
return void 0;
|
|
12302
|
+
}
|
|
12303
|
+
// ============================================================
|
|
12304
|
+
// 私有方法
|
|
12305
|
+
// ============================================================
|
|
12306
|
+
/**
|
|
12307
|
+
* 按 order 排序拦截器
|
|
12308
|
+
*/
|
|
12309
|
+
sortInterceptors(interceptors) {
|
|
12310
|
+
interceptors.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
12311
|
+
}
|
|
12312
|
+
/**
|
|
12313
|
+
* 按名称移除拦截器
|
|
12314
|
+
*/
|
|
12315
|
+
removeByName(interceptors, name) {
|
|
12316
|
+
const index = interceptors.findIndex((i) => i.name === name);
|
|
12317
|
+
if (index !== -1) {
|
|
12318
|
+
interceptors.splice(index, 1);
|
|
11714
12319
|
return true;
|
|
11715
12320
|
}
|
|
11716
|
-
|
|
11717
|
-
|
|
12321
|
+
return false;
|
|
12322
|
+
}
|
|
12323
|
+
};
|
|
12324
|
+
function createInterceptorManager() {
|
|
12325
|
+
return new InterceptorManager();
|
|
12326
|
+
}
|
|
12327
|
+
|
|
12328
|
+
// src/interceptors/request/auth.interceptor.ts
|
|
12329
|
+
var AuthInterceptor = class {
|
|
12330
|
+
constructor(config) {
|
|
12331
|
+
this.name = "auth";
|
|
12332
|
+
this.authenticator = config.authenticator;
|
|
12333
|
+
this.order = config.order ?? -100;
|
|
12334
|
+
}
|
|
12335
|
+
async intercept(context) {
|
|
12336
|
+
const { options } = context;
|
|
12337
|
+
if (options.skipAuth) {
|
|
12338
|
+
return options;
|
|
12339
|
+
}
|
|
12340
|
+
await this.authenticator.authenticate(options.headers);
|
|
12341
|
+
return options;
|
|
12342
|
+
}
|
|
12343
|
+
};
|
|
12344
|
+
function createAuthInterceptor(config) {
|
|
12345
|
+
return new AuthInterceptor(config);
|
|
12346
|
+
}
|
|
12347
|
+
|
|
12348
|
+
// src/utils/request-id.ts
|
|
12349
|
+
function generateRequestId() {
|
|
12350
|
+
const timestamp = Date.now().toString(36);
|
|
12351
|
+
const random = Math.random().toString(36).slice(2, 9);
|
|
12352
|
+
return `req_${timestamp}_${random}`;
|
|
12353
|
+
}
|
|
12354
|
+
function generateTraceId() {
|
|
12355
|
+
const timestamp = Date.now().toString(36);
|
|
12356
|
+
const random1 = Math.random().toString(36).slice(2, 9);
|
|
12357
|
+
const random2 = Math.random().toString(36).slice(2, 9);
|
|
12358
|
+
return `trace_${timestamp}_${random1}_${random2}`;
|
|
12359
|
+
}
|
|
12360
|
+
function isValidRequestId(id) {
|
|
12361
|
+
return /^req_[a-z0-9]+_[a-z0-9]+$/.test(id);
|
|
12362
|
+
}
|
|
12363
|
+
function isValidTraceId(id) {
|
|
12364
|
+
return /^trace_[a-z0-9]+_[a-z0-9]+_[a-z0-9]+$/.test(id);
|
|
12365
|
+
}
|
|
12366
|
+
|
|
12367
|
+
// src/interceptors/request/request-id.interceptor.ts
|
|
12368
|
+
var RequestIdInterceptor = class {
|
|
12369
|
+
constructor(config = {}) {
|
|
12370
|
+
this.name = "request-id";
|
|
12371
|
+
this.headerName = config.headerName ?? "X-Request-ID";
|
|
12372
|
+
this.generateId = config.generateId ?? generateRequestId;
|
|
12373
|
+
this.order = config.order ?? -90;
|
|
12374
|
+
}
|
|
12375
|
+
intercept(context) {
|
|
12376
|
+
const { options } = context;
|
|
12377
|
+
if (!options.headers[this.headerName]) {
|
|
12378
|
+
options.headers[this.headerName] = context.requestId;
|
|
12379
|
+
}
|
|
12380
|
+
return options;
|
|
12381
|
+
}
|
|
12382
|
+
};
|
|
12383
|
+
function createRequestIdInterceptor(config) {
|
|
12384
|
+
return new RequestIdInterceptor(config);
|
|
12385
|
+
}
|
|
12386
|
+
|
|
12387
|
+
// src/interceptors/request/trace.interceptor.ts
|
|
12388
|
+
var TraceInterceptor = class {
|
|
12389
|
+
constructor(config = {}) {
|
|
12390
|
+
this.name = "trace";
|
|
12391
|
+
this.traceIdHeader = config.traceIdHeader ?? "X-Trace-ID";
|
|
12392
|
+
this.addTraceparent = config.addTraceparent ?? false;
|
|
12393
|
+
this.getTraceId = config.getTraceId;
|
|
12394
|
+
this.getTraceparent = config.getTraceparent;
|
|
12395
|
+
this.order = config.order ?? -80;
|
|
12396
|
+
}
|
|
12397
|
+
intercept(context) {
|
|
12398
|
+
const { options } = context;
|
|
12399
|
+
if (!options.headers[this.traceIdHeader]) {
|
|
12400
|
+
const traceId = this.getTraceId?.() ?? context.traceId ?? generateTraceId();
|
|
12401
|
+
options.headers[this.traceIdHeader] = traceId;
|
|
12402
|
+
}
|
|
12403
|
+
if (this.addTraceparent && this.getTraceparent) {
|
|
12404
|
+
const traceparent = this.getTraceparent();
|
|
12405
|
+
if (traceparent && !options.headers["traceparent"]) {
|
|
12406
|
+
options.headers["traceparent"] = traceparent;
|
|
12407
|
+
}
|
|
12408
|
+
}
|
|
12409
|
+
return options;
|
|
12410
|
+
}
|
|
12411
|
+
};
|
|
12412
|
+
function createTraceInterceptor(config) {
|
|
12413
|
+
return new TraceInterceptor(config);
|
|
12414
|
+
}
|
|
12415
|
+
|
|
12416
|
+
// src/interceptors/request/timeout.interceptor.ts
|
|
12417
|
+
var TimeoutInterceptor = class {
|
|
12418
|
+
constructor(config = {}) {
|
|
12419
|
+
this.name = "timeout";
|
|
12420
|
+
this.defaultTimeout = config.defaultTimeout ?? 3e4;
|
|
12421
|
+
this.methodTimeouts = config.methodTimeouts ?? {};
|
|
12422
|
+
this.pathTimeouts = config.pathTimeouts ?? [];
|
|
12423
|
+
this.order = config.order ?? -70;
|
|
12424
|
+
}
|
|
12425
|
+
intercept(context) {
|
|
12426
|
+
const { options } = context;
|
|
12427
|
+
if (options.timeout !== void 0) {
|
|
12428
|
+
return options;
|
|
12429
|
+
}
|
|
12430
|
+
for (const { pattern, timeout } of this.pathTimeouts) {
|
|
12431
|
+
if (pattern.test(options.path)) {
|
|
12432
|
+
options.timeout = timeout;
|
|
12433
|
+
return options;
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12436
|
+
const methodTimeout = this.methodTimeouts[options.method];
|
|
12437
|
+
if (methodTimeout !== void 0) {
|
|
12438
|
+
options.timeout = methodTimeout;
|
|
12439
|
+
return options;
|
|
12440
|
+
}
|
|
12441
|
+
options.timeout = this.defaultTimeout;
|
|
12442
|
+
return options;
|
|
12443
|
+
}
|
|
12444
|
+
};
|
|
12445
|
+
function createTimeoutInterceptor(config) {
|
|
12446
|
+
return new TimeoutInterceptor(config);
|
|
12447
|
+
}
|
|
12448
|
+
|
|
12449
|
+
// src/utils/retry-delay.ts
|
|
12450
|
+
function calculateRetryDelay(config, attempt) {
|
|
12451
|
+
const baseDelay = calculateBaseDelay(config.backoffStrategy, config.initialDelayMs, attempt);
|
|
12452
|
+
const jitterFactor = config.jitterFactor ?? 0.1;
|
|
12453
|
+
const delayWithJitter = addJitter(baseDelay, jitterFactor);
|
|
12454
|
+
return Math.min(delayWithJitter, config.maxDelayMs);
|
|
12455
|
+
}
|
|
12456
|
+
function calculateBaseDelay(strategy, initialDelayMs, attempt) {
|
|
12457
|
+
switch (strategy) {
|
|
12458
|
+
case "fixed":
|
|
12459
|
+
return initialDelayMs;
|
|
12460
|
+
case "linear":
|
|
12461
|
+
return initialDelayMs * (attempt + 1);
|
|
12462
|
+
case "exponential":
|
|
12463
|
+
return initialDelayMs * Math.pow(2, attempt);
|
|
12464
|
+
default: {
|
|
12465
|
+
const _exhaustiveCheck = strategy;
|
|
12466
|
+
throw new Error(`Unknown backoff strategy: ${_exhaustiveCheck}`);
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
12469
|
+
}
|
|
12470
|
+
function addJitter(delay, factor) {
|
|
12471
|
+
const validFactor = Math.max(0, Math.min(1, factor));
|
|
12472
|
+
const jitterRange = delay * validFactor;
|
|
12473
|
+
const jitter = (Math.random() - 0.5) * 2 * jitterRange;
|
|
12474
|
+
return Math.max(0, delay + jitter);
|
|
12475
|
+
}
|
|
12476
|
+
function parseRetryAfter(retryAfter) {
|
|
12477
|
+
if (!retryAfter) {
|
|
12478
|
+
return void 0;
|
|
12479
|
+
}
|
|
12480
|
+
const seconds = parseInt(retryAfter, 10);
|
|
12481
|
+
if (!isNaN(seconds) && seconds >= 0) {
|
|
12482
|
+
return seconds;
|
|
12483
|
+
}
|
|
12484
|
+
const date = new Date(retryAfter);
|
|
12485
|
+
if (!isNaN(date.getTime())) {
|
|
12486
|
+
const delayMs = date.getTime() - Date.now();
|
|
12487
|
+
return Math.max(0, Math.ceil(delayMs / 1e3));
|
|
12488
|
+
}
|
|
12489
|
+
return void 0;
|
|
12490
|
+
}
|
|
12491
|
+
function getRecommendedRetryDelay(config, attempt, retryAfterSeconds) {
|
|
12492
|
+
if (config.respectRetryAfter !== false && retryAfterSeconds !== void 0) {
|
|
12493
|
+
const retryAfterMs = retryAfterSeconds * 1e3;
|
|
12494
|
+
return Math.min(retryAfterMs, config.maxDelayMs);
|
|
12495
|
+
}
|
|
12496
|
+
return calculateRetryDelay(config, attempt);
|
|
12497
|
+
}
|
|
12498
|
+
|
|
12499
|
+
// src/utils/sleep.ts
|
|
12500
|
+
function sleep(ms) {
|
|
12501
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12502
|
+
}
|
|
12503
|
+
function sleepWithAbort(ms, signal) {
|
|
12504
|
+
return new Promise((resolve, reject) => {
|
|
12505
|
+
if (signal?.aborted) {
|
|
12506
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
12507
|
+
return;
|
|
12508
|
+
}
|
|
12509
|
+
const timeoutId = setTimeout(() => {
|
|
12510
|
+
resolve();
|
|
12511
|
+
}, ms);
|
|
12512
|
+
if (signal) {
|
|
12513
|
+
const abortHandler = () => {
|
|
12514
|
+
clearTimeout(timeoutId);
|
|
12515
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
12516
|
+
};
|
|
12517
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
12518
|
+
}
|
|
12519
|
+
});
|
|
12520
|
+
}
|
|
12521
|
+
|
|
12522
|
+
// src/interceptors/response/retry.interceptor.ts
|
|
12523
|
+
var RetryInterceptor = class {
|
|
12524
|
+
constructor(config) {
|
|
12525
|
+
this.name = "retry";
|
|
12526
|
+
this.config = {
|
|
12527
|
+
...DEFAULT_RETRY_CONFIG,
|
|
12528
|
+
maxRetries: config.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
|
|
12529
|
+
initialDelayMs: config.initialDelayMs ?? DEFAULT_RETRY_CONFIG.initialDelayMs,
|
|
12530
|
+
maxDelayMs: config.maxDelayMs ?? DEFAULT_RETRY_CONFIG.maxDelayMs,
|
|
12531
|
+
backoffStrategy: config.backoffStrategy ?? DEFAULT_RETRY_CONFIG.backoffStrategy,
|
|
12532
|
+
retryableStatusCodes: config.retryableStatusCodes ?? DEFAULT_RETRY_CONFIG.retryableStatusCodes,
|
|
12533
|
+
retryOnNetworkError: config.retryOnNetworkError ?? DEFAULT_RETRY_CONFIG.retryOnNetworkError,
|
|
12534
|
+
retryOnTimeout: config.retryOnTimeout ?? DEFAULT_RETRY_CONFIG.retryOnTimeout,
|
|
12535
|
+
respectRetryAfter: config.respectRetryAfter ?? DEFAULT_RETRY_CONFIG.respectRetryAfter,
|
|
12536
|
+
jitterFactor: config.jitterFactor ?? DEFAULT_RETRY_CONFIG.jitterFactor,
|
|
12537
|
+
onRetry: config.onRetry,
|
|
12538
|
+
shouldRetry: config.shouldRetry
|
|
12539
|
+
};
|
|
12540
|
+
this.executeRequest = config.executeRequest;
|
|
12541
|
+
this.order = config.order ?? 100;
|
|
12542
|
+
}
|
|
12543
|
+
async intercept(error, context) {
|
|
12544
|
+
if (!this.shouldRetry(error, context)) {
|
|
12545
|
+
return void 0;
|
|
12546
|
+
}
|
|
12547
|
+
const retryAfterSeconds = error instanceof ApiError ? error.getRetryAfter() : void 0;
|
|
12548
|
+
const delayMs = getRecommendedRetryDelay(this.config, context.retryCount, retryAfterSeconds);
|
|
12549
|
+
if (this.config.onRetry) {
|
|
12550
|
+
const retryInfo = {
|
|
12551
|
+
attempt: context.retryCount + 1,
|
|
12552
|
+
maxRetries: this.config.maxRetries,
|
|
12553
|
+
delayMs,
|
|
12554
|
+
error,
|
|
12555
|
+
url: context.url,
|
|
12556
|
+
method: context.options.method
|
|
12557
|
+
};
|
|
12558
|
+
this.config.onRetry(retryInfo);
|
|
12559
|
+
}
|
|
12560
|
+
await sleep(delayMs);
|
|
12561
|
+
context.retryCount++;
|
|
12562
|
+
return this.executeRequest(context);
|
|
12563
|
+
}
|
|
12564
|
+
/**
|
|
12565
|
+
* 判断是否应该重试
|
|
12566
|
+
*/
|
|
12567
|
+
shouldRetry(error, context) {
|
|
12568
|
+
if (context.retryCount >= this.config.maxRetries) {
|
|
12569
|
+
return false;
|
|
12570
|
+
}
|
|
12571
|
+
if (error instanceof AbortError) {
|
|
12572
|
+
return false;
|
|
12573
|
+
}
|
|
12574
|
+
if (this.config.shouldRetry) {
|
|
12575
|
+
const statusCode = error instanceof ApiError ? error.statusCode : void 0;
|
|
12576
|
+
const retryAfter = error instanceof ApiError ? error.getRetryAfter() : void 0;
|
|
12577
|
+
return this.config.shouldRetry(error, context.retryCount, {
|
|
12578
|
+
url: context.url,
|
|
12579
|
+
method: context.options.method,
|
|
12580
|
+
statusCode,
|
|
12581
|
+
retryAfter
|
|
12582
|
+
});
|
|
12583
|
+
}
|
|
12584
|
+
if (error instanceof NetworkError) {
|
|
12585
|
+
return this.config.retryOnNetworkError !== false;
|
|
12586
|
+
}
|
|
12587
|
+
if (error instanceof TimeoutError) {
|
|
12588
|
+
return this.config.retryOnTimeout !== false;
|
|
12589
|
+
}
|
|
12590
|
+
if (error instanceof ApiError) {
|
|
12591
|
+
const retryableCodes = this.config.retryableStatusCodes ?? [];
|
|
12592
|
+
return retryableCodes.includes(error.statusCode);
|
|
12593
|
+
}
|
|
12594
|
+
return false;
|
|
12595
|
+
}
|
|
12596
|
+
};
|
|
12597
|
+
function createRetryInterceptor(config) {
|
|
12598
|
+
return new RetryInterceptor(config);
|
|
12599
|
+
}
|
|
12600
|
+
|
|
12601
|
+
// src/interceptors/response/token-refresh.interceptor.ts
|
|
12602
|
+
var TokenRefreshInterceptor = class {
|
|
12603
|
+
constructor(config) {
|
|
12604
|
+
this.name = "token-refresh";
|
|
12605
|
+
this.state = {
|
|
12606
|
+
isRefreshing: false,
|
|
12607
|
+
refreshPromise: null,
|
|
12608
|
+
failCount: 0
|
|
12609
|
+
};
|
|
12610
|
+
this.refreshToken = config.refreshToken;
|
|
12611
|
+
this.executeRequest = config.executeRequest;
|
|
12612
|
+
this.triggerStatusCodes = config.triggerStatusCodes ?? [401];
|
|
12613
|
+
this.maxRetries = config.maxRetries ?? 1;
|
|
12614
|
+
this.onTokenRefreshed = config.onTokenRefreshed;
|
|
12615
|
+
this.onRefreshFailed = config.onRefreshFailed;
|
|
12616
|
+
this.order = config.order ?? 50;
|
|
12617
|
+
}
|
|
12618
|
+
async intercept(error, context) {
|
|
12619
|
+
if (!(error instanceof ApiError)) {
|
|
12620
|
+
return void 0;
|
|
12621
|
+
}
|
|
12622
|
+
if (!this.shouldRefresh(error, context)) {
|
|
12623
|
+
return void 0;
|
|
12624
|
+
}
|
|
12625
|
+
try {
|
|
12626
|
+
const newToken = await this.doRefresh();
|
|
12627
|
+
this.onTokenRefreshed?.(newToken);
|
|
12628
|
+
this.state.failCount = 0;
|
|
12629
|
+
return this.executeRequest(context);
|
|
12630
|
+
} catch (refreshError) {
|
|
12631
|
+
this.state.failCount++;
|
|
12632
|
+
this.onRefreshFailed?.(refreshError);
|
|
12633
|
+
return void 0;
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12636
|
+
/**
|
|
12637
|
+
* 判断是否应该刷新 Token
|
|
12638
|
+
*/
|
|
12639
|
+
shouldRefresh(error, context) {
|
|
12640
|
+
if (!this.triggerStatusCodes.includes(error.statusCode)) {
|
|
12641
|
+
return false;
|
|
12642
|
+
}
|
|
12643
|
+
if (this.state.failCount >= this.maxRetries) {
|
|
12644
|
+
return false;
|
|
12645
|
+
}
|
|
12646
|
+
if (context.metadata["tokenRefreshAttempted"]) {
|
|
12647
|
+
return false;
|
|
12648
|
+
}
|
|
12649
|
+
return true;
|
|
12650
|
+
}
|
|
12651
|
+
/**
|
|
12652
|
+
* 执行 Token 刷新
|
|
12653
|
+
*
|
|
12654
|
+
* 合并并发请求,确保同一时间只有一个刷新请求
|
|
12655
|
+
*/
|
|
12656
|
+
async doRefresh() {
|
|
12657
|
+
if (this.state.isRefreshing && this.state.refreshPromise) {
|
|
12658
|
+
return this.state.refreshPromise;
|
|
12659
|
+
}
|
|
12660
|
+
this.state.isRefreshing = true;
|
|
12661
|
+
this.state.refreshPromise = this.refreshToken();
|
|
12662
|
+
try {
|
|
12663
|
+
const newToken = await this.state.refreshPromise;
|
|
12664
|
+
return newToken;
|
|
12665
|
+
} finally {
|
|
12666
|
+
this.state.isRefreshing = false;
|
|
12667
|
+
this.state.refreshPromise = null;
|
|
12668
|
+
}
|
|
12669
|
+
}
|
|
12670
|
+
/**
|
|
12671
|
+
* 重置状态(用于测试或手动重置)
|
|
12672
|
+
*/
|
|
12673
|
+
reset() {
|
|
12674
|
+
this.state.isRefreshing = false;
|
|
12675
|
+
this.state.refreshPromise = null;
|
|
12676
|
+
this.state.failCount = 0;
|
|
12677
|
+
}
|
|
12678
|
+
};
|
|
12679
|
+
function createTokenRefreshInterceptor(config) {
|
|
12680
|
+
return new TokenRefreshInterceptor(config);
|
|
12681
|
+
}
|
|
12682
|
+
|
|
12683
|
+
// src/interceptors/response/error-transform.interceptor.ts
|
|
12684
|
+
var ErrorTransformInterceptor = class {
|
|
12685
|
+
constructor(config = {}) {
|
|
12686
|
+
this.name = "error-transform";
|
|
12687
|
+
this.includeRawResponse = config.includeRawResponse ?? false;
|
|
12688
|
+
this.extractErrorMessage = config.extractErrorMessage;
|
|
12689
|
+
this.extractErrorCode = config.extractErrorCode;
|
|
12690
|
+
this.order = config.order ?? 0;
|
|
12691
|
+
}
|
|
12692
|
+
intercept(response, _context) {
|
|
12693
|
+
return response;
|
|
12694
|
+
}
|
|
12695
|
+
};
|
|
12696
|
+
function createErrorTransformInterceptor(config) {
|
|
12697
|
+
return new ErrorTransformInterceptor(config);
|
|
12698
|
+
}
|
|
12699
|
+
|
|
12700
|
+
// src/interceptors/error/logging.interceptor.ts
|
|
12701
|
+
var LoggingInterceptor = class {
|
|
12702
|
+
constructor(config) {
|
|
12703
|
+
this.name = "logging";
|
|
12704
|
+
this.logger = config.logger;
|
|
12705
|
+
this.verbose = config.verbose ?? false;
|
|
12706
|
+
this.logAborted = config.logAborted ?? false;
|
|
12707
|
+
this.filter = config.filter;
|
|
12708
|
+
this.order = config.order ?? -100;
|
|
12709
|
+
}
|
|
12710
|
+
intercept(error, context) {
|
|
12711
|
+
if (this.filter && !this.filter(error, context)) {
|
|
12712
|
+
return void 0;
|
|
12713
|
+
}
|
|
12714
|
+
if (error instanceof AbortError && !this.logAborted) {
|
|
12715
|
+
return void 0;
|
|
12716
|
+
}
|
|
12717
|
+
const logInfo = this.buildLogInfo(error, context);
|
|
12718
|
+
if (error instanceof ApiError && error.isClientError() && !error.isRateLimited()) {
|
|
12719
|
+
this.logger.warn(`[API Error] ${logInfo.summary}`, logInfo);
|
|
12720
|
+
} else if (error instanceof AbortError) {
|
|
12721
|
+
this.logger.debug(`[Aborted] ${logInfo.summary}`, logInfo);
|
|
12722
|
+
} else {
|
|
12723
|
+
this.logger.error(`[Error] ${logInfo.summary}`, logInfo);
|
|
12724
|
+
}
|
|
12725
|
+
return void 0;
|
|
12726
|
+
}
|
|
12727
|
+
/**
|
|
12728
|
+
* 构建日志信息
|
|
12729
|
+
*/
|
|
12730
|
+
buildLogInfo(error, context) {
|
|
12731
|
+
const duration = Date.now() - context.startTime;
|
|
12732
|
+
const base = {
|
|
12733
|
+
summary: `${context.options.method} ${context.options.path} - ${error.message}`,
|
|
12734
|
+
requestId: context.requestId,
|
|
12735
|
+
traceId: context.traceId,
|
|
12736
|
+
method: context.options.method,
|
|
12737
|
+
path: context.options.path,
|
|
12738
|
+
url: context.url,
|
|
12739
|
+
duration: `${duration}ms`,
|
|
12740
|
+
retryCount: context.retryCount
|
|
12741
|
+
};
|
|
12742
|
+
if (this.verbose) {
|
|
12743
|
+
const detailed = {
|
|
12744
|
+
...base,
|
|
12745
|
+
error: {
|
|
12746
|
+
name: error.name,
|
|
12747
|
+
message: error.message,
|
|
12748
|
+
stack: error.stack
|
|
12749
|
+
}
|
|
12750
|
+
};
|
|
12751
|
+
if (error instanceof ApiError) {
|
|
12752
|
+
detailed.apiError = {
|
|
12753
|
+
code: error.code,
|
|
12754
|
+
statusCode: error.statusCode,
|
|
12755
|
+
details: error.details,
|
|
12756
|
+
retryable: error.retryable
|
|
12757
|
+
};
|
|
12758
|
+
} else if (error instanceof TimeoutError) {
|
|
12759
|
+
detailed.timeout = error.timeoutMs;
|
|
12760
|
+
} else if (error instanceof NetworkError) {
|
|
12761
|
+
detailed.networkError = error.cause?.message;
|
|
12762
|
+
}
|
|
12763
|
+
return detailed;
|
|
12764
|
+
}
|
|
12765
|
+
if (error instanceof ApiError) {
|
|
12766
|
+
return {
|
|
12767
|
+
...base,
|
|
12768
|
+
code: error.code,
|
|
12769
|
+
statusCode: error.statusCode
|
|
12770
|
+
};
|
|
12771
|
+
}
|
|
12772
|
+
if (error instanceof TimeoutError) {
|
|
12773
|
+
return {
|
|
12774
|
+
...base,
|
|
12775
|
+
timeout: error.timeoutMs
|
|
12776
|
+
};
|
|
12777
|
+
}
|
|
12778
|
+
return base;
|
|
12779
|
+
}
|
|
12780
|
+
};
|
|
12781
|
+
function createLoggingInterceptor(config) {
|
|
12782
|
+
return new LoggingInterceptor(config);
|
|
12783
|
+
}
|
|
12784
|
+
|
|
12785
|
+
// src/interceptors/error/reporting.interceptor.ts
|
|
12786
|
+
var ReportingInterceptor = class {
|
|
12787
|
+
constructor(config) {
|
|
12788
|
+
this.name = "reporting";
|
|
12789
|
+
this.reporter = config.reporter;
|
|
12790
|
+
this.getSeverity = config.getSeverity ?? this.defaultGetSeverity;
|
|
12791
|
+
this.reportClientErrors = config.reportClientErrors ?? false;
|
|
12792
|
+
this.reportAborted = config.reportAborted ?? false;
|
|
12793
|
+
this.reportNetworkErrors = config.reportNetworkErrors ?? true;
|
|
12794
|
+
this.sampleRate = config.sampleRate ?? 1;
|
|
12795
|
+
this.extractContext = config.extractContext;
|
|
12796
|
+
this.order = config.order ?? -50;
|
|
12797
|
+
}
|
|
12798
|
+
intercept(error, context) {
|
|
12799
|
+
if (!this.shouldReport(error)) {
|
|
12800
|
+
return void 0;
|
|
12801
|
+
}
|
|
12802
|
+
if (this.sampleRate < 1 && Math.random() > this.sampleRate) {
|
|
12803
|
+
return void 0;
|
|
12804
|
+
}
|
|
12805
|
+
const reportContext = this.buildReportContext(error, context);
|
|
12806
|
+
try {
|
|
12807
|
+
this.reporter.report(error, reportContext);
|
|
12808
|
+
} catch {
|
|
12809
|
+
}
|
|
12810
|
+
return void 0;
|
|
12811
|
+
}
|
|
12812
|
+
/**
|
|
12813
|
+
* 判断是否应该上报
|
|
12814
|
+
*/
|
|
12815
|
+
shouldReport(error) {
|
|
12816
|
+
if (error instanceof AbortError) {
|
|
12817
|
+
return this.reportAborted;
|
|
12818
|
+
}
|
|
12819
|
+
if (error instanceof NetworkError) {
|
|
12820
|
+
return this.reportNetworkErrors;
|
|
12821
|
+
}
|
|
12822
|
+
if (error instanceof ApiError) {
|
|
12823
|
+
if (error.isClientError()) {
|
|
12824
|
+
return this.reportClientErrors;
|
|
12825
|
+
}
|
|
12826
|
+
return true;
|
|
12827
|
+
}
|
|
12828
|
+
if (error instanceof TimeoutError) {
|
|
12829
|
+
return true;
|
|
12830
|
+
}
|
|
12831
|
+
return true;
|
|
12832
|
+
}
|
|
12833
|
+
/**
|
|
12834
|
+
* 构建上报上下文
|
|
12835
|
+
*/
|
|
12836
|
+
buildReportContext(error, context) {
|
|
12837
|
+
const baseContext = {
|
|
12838
|
+
severity: this.getSeverity(error),
|
|
12839
|
+
requestId: context.requestId,
|
|
12840
|
+
traceId: context.traceId,
|
|
12841
|
+
url: context.url,
|
|
12842
|
+
method: context.options.method,
|
|
12843
|
+
path: context.options.path,
|
|
12844
|
+
retryCount: context.retryCount,
|
|
12845
|
+
duration: Date.now() - context.startTime
|
|
12846
|
+
};
|
|
12847
|
+
if (error instanceof ApiError) {
|
|
12848
|
+
baseContext["api"] = {
|
|
12849
|
+
code: error.code,
|
|
12850
|
+
statusCode: error.statusCode,
|
|
12851
|
+
retryable: error.retryable
|
|
12852
|
+
};
|
|
12853
|
+
} else if (error instanceof TimeoutError) {
|
|
12854
|
+
baseContext["timeout"] = error.timeoutMs;
|
|
12855
|
+
}
|
|
12856
|
+
if (this.extractContext) {
|
|
12857
|
+
const customContext = this.extractContext(error, context);
|
|
12858
|
+
return { ...baseContext, ...customContext };
|
|
12859
|
+
}
|
|
12860
|
+
return baseContext;
|
|
12861
|
+
}
|
|
12862
|
+
/**
|
|
12863
|
+
* 默认的严重程度判断
|
|
12864
|
+
*/
|
|
12865
|
+
defaultGetSeverity(error) {
|
|
12866
|
+
if (error instanceof AbortError) {
|
|
12867
|
+
return "debug";
|
|
12868
|
+
}
|
|
12869
|
+
if (error instanceof ApiError) {
|
|
12870
|
+
if (error.isServerError()) {
|
|
12871
|
+
return "error";
|
|
12872
|
+
}
|
|
12873
|
+
if (error.isRateLimited()) {
|
|
12874
|
+
return "warning";
|
|
12875
|
+
}
|
|
12876
|
+
return "info";
|
|
12877
|
+
}
|
|
12878
|
+
if (error instanceof NetworkError || error instanceof TimeoutError) {
|
|
12879
|
+
return "warning";
|
|
12880
|
+
}
|
|
12881
|
+
return "error";
|
|
12882
|
+
}
|
|
12883
|
+
};
|
|
12884
|
+
function createReportingInterceptor(config) {
|
|
12885
|
+
return new ReportingInterceptor(config);
|
|
12886
|
+
}
|
|
12887
|
+
|
|
12888
|
+
// src/plugins/deduplication.ts
|
|
12889
|
+
var RequestDeduper = class {
|
|
12890
|
+
constructor(config = {}) {
|
|
12891
|
+
this.inflightRequests = /* @__PURE__ */ new Map();
|
|
12892
|
+
this.getOnly = config.getOnly ?? true;
|
|
12893
|
+
this.generateKey = config.generateKey ?? this.defaultGenerateKey;
|
|
12894
|
+
this.maxSize = config.maxSize ?? 100;
|
|
12895
|
+
}
|
|
12896
|
+
/**
|
|
12897
|
+
* 执行请求(带去重)
|
|
12898
|
+
*
|
|
12899
|
+
* @param method - HTTP 方法
|
|
12900
|
+
* @param url - 请求 URL
|
|
12901
|
+
* @param execute - 实际执行请求的函数
|
|
12902
|
+
* @returns 请求结果
|
|
12903
|
+
*/
|
|
12904
|
+
async execute(method, url2, execute) {
|
|
12905
|
+
if (!this.shouldDeduplicate(method)) {
|
|
12906
|
+
return execute();
|
|
12907
|
+
}
|
|
12908
|
+
const key = this.generateKey(method, url2);
|
|
12909
|
+
const inflight = this.inflightRequests.get(key);
|
|
12910
|
+
if (inflight) {
|
|
12911
|
+
return inflight.promise;
|
|
12912
|
+
}
|
|
12913
|
+
if (this.inflightRequests.size >= this.maxSize) {
|
|
12914
|
+
this.cleanup();
|
|
12915
|
+
}
|
|
12916
|
+
const promise = execute().finally(() => {
|
|
12917
|
+
this.inflightRequests.delete(key);
|
|
12918
|
+
});
|
|
12919
|
+
this.inflightRequests.set(key, {
|
|
12920
|
+
promise,
|
|
12921
|
+
createdAt: Date.now()
|
|
12922
|
+
});
|
|
12923
|
+
return promise;
|
|
12924
|
+
}
|
|
12925
|
+
/**
|
|
12926
|
+
* 包装 fetch 函数
|
|
12927
|
+
*
|
|
12928
|
+
* @param fetch - 原始 fetch 函数
|
|
12929
|
+
* @returns 去重的 fetch 函数
|
|
12930
|
+
*/
|
|
12931
|
+
wrap(fetch2) {
|
|
12932
|
+
return (url2, init) => {
|
|
12933
|
+
const method = init?.method ?? "GET";
|
|
12934
|
+
return this.execute(method, url2, () => fetch2(url2, init));
|
|
12935
|
+
};
|
|
12936
|
+
}
|
|
12937
|
+
/**
|
|
12938
|
+
* 获取当前飞行中的请求数量
|
|
12939
|
+
*/
|
|
12940
|
+
get inflightCount() {
|
|
12941
|
+
return this.inflightRequests.size;
|
|
12942
|
+
}
|
|
12943
|
+
/**
|
|
12944
|
+
* 清空所有飞行中的请求
|
|
12945
|
+
*/
|
|
12946
|
+
clear() {
|
|
12947
|
+
this.inflightRequests.clear();
|
|
12948
|
+
}
|
|
12949
|
+
/**
|
|
12950
|
+
* 判断是否应该去重
|
|
12951
|
+
*/
|
|
12952
|
+
shouldDeduplicate(method) {
|
|
12953
|
+
if (this.getOnly) {
|
|
12954
|
+
return method.toUpperCase() === "GET";
|
|
12955
|
+
}
|
|
12956
|
+
return true;
|
|
12957
|
+
}
|
|
12958
|
+
/**
|
|
12959
|
+
* 默认的键生成函数
|
|
12960
|
+
*/
|
|
12961
|
+
defaultGenerateKey(method, url2) {
|
|
12962
|
+
return `${method.toUpperCase()}:${url2}`;
|
|
12963
|
+
}
|
|
12964
|
+
/**
|
|
12965
|
+
* 清理过期的请求
|
|
12966
|
+
*/
|
|
12967
|
+
cleanup() {
|
|
12968
|
+
const toDelete = Math.floor(this.maxSize * 0.2);
|
|
12969
|
+
const entries = Array.from(this.inflightRequests.entries()).sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
12970
|
+
for (let i = 0; i < toDelete && i < entries.length; i++) {
|
|
12971
|
+
this.inflightRequests.delete(entries[i][0]);
|
|
12972
|
+
}
|
|
12973
|
+
}
|
|
12974
|
+
};
|
|
12975
|
+
function createRequestDeduper(config) {
|
|
12976
|
+
return new RequestDeduper(config);
|
|
12977
|
+
}
|
|
12978
|
+
|
|
12979
|
+
// src/utils/url.ts
|
|
12980
|
+
function buildUrl(baseUrl, path, query) {
|
|
12981
|
+
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
12982
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
12983
|
+
const url2 = new URL(`${normalizedBase}${normalizedPath}`);
|
|
12984
|
+
if (query) {
|
|
12985
|
+
for (const [key, value] of Object.entries(query)) {
|
|
12986
|
+
if (value !== void 0 && value !== null) {
|
|
12987
|
+
url2.searchParams.set(key, String(value));
|
|
12988
|
+
}
|
|
12989
|
+
}
|
|
12990
|
+
}
|
|
12991
|
+
return url2.toString();
|
|
12992
|
+
}
|
|
12993
|
+
function extractPath(url2) {
|
|
12994
|
+
try {
|
|
12995
|
+
const parsed = new URL(url2);
|
|
12996
|
+
return parsed.pathname;
|
|
12997
|
+
} catch {
|
|
12998
|
+
const queryIndex = url2.indexOf("?");
|
|
12999
|
+
const pathPart = queryIndex >= 0 ? url2.slice(0, queryIndex) : url2;
|
|
13000
|
+
return pathPart.startsWith("/") ? pathPart : `/${pathPart}`;
|
|
13001
|
+
}
|
|
13002
|
+
}
|
|
13003
|
+
function parseQueryString(queryString) {
|
|
13004
|
+
const result = {};
|
|
13005
|
+
const normalized = queryString.startsWith("?") ? queryString.slice(1) : queryString;
|
|
13006
|
+
if (!normalized) {
|
|
13007
|
+
return result;
|
|
13008
|
+
}
|
|
13009
|
+
const params = new URLSearchParams(normalized);
|
|
13010
|
+
for (const [key, value] of params.entries()) {
|
|
13011
|
+
result[key] = value;
|
|
13012
|
+
}
|
|
13013
|
+
return result;
|
|
13014
|
+
}
|
|
13015
|
+
function joinPaths(base, path) {
|
|
13016
|
+
const normalizedBase = base.replace(/\/+$/, "");
|
|
13017
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
13018
|
+
if (!normalizedPath) {
|
|
13019
|
+
return normalizedBase || "/";
|
|
13020
|
+
}
|
|
13021
|
+
return `${normalizedBase}/${normalizedPath}`;
|
|
13022
|
+
}
|
|
13023
|
+
|
|
13024
|
+
// src/plugins/metrics.ts
|
|
13025
|
+
var DefaultMetricsCollector = class {
|
|
13026
|
+
constructor(config = {}) {
|
|
13027
|
+
this.metrics = [];
|
|
13028
|
+
this.maxMetrics = config.maxMetrics ?? 1e3;
|
|
13029
|
+
this.ttlMs = config.ttlMs ?? 36e5;
|
|
13030
|
+
this.onMetrics = config.onMetrics;
|
|
13031
|
+
}
|
|
13032
|
+
record(metrics) {
|
|
13033
|
+
this.onMetrics?.(metrics);
|
|
13034
|
+
this.metrics.push(metrics);
|
|
13035
|
+
this.cleanup();
|
|
13036
|
+
}
|
|
13037
|
+
summary() {
|
|
13038
|
+
if (this.metrics.length === 0) {
|
|
13039
|
+
return this.emptySummary();
|
|
13040
|
+
}
|
|
13041
|
+
const successful = this.metrics.filter((m) => m.success);
|
|
13042
|
+
const durations = this.metrics.map((m) => m.durationMs).sort((a, b) => a - b);
|
|
13043
|
+
const p = (percentile) => {
|
|
13044
|
+
const index = Math.ceil(durations.length * percentile / 100) - 1;
|
|
13045
|
+
return durations[Math.max(0, index)];
|
|
13046
|
+
};
|
|
13047
|
+
const statusCodeDistribution = {};
|
|
13048
|
+
for (const m of this.metrics) {
|
|
13049
|
+
if (m.status !== void 0) {
|
|
13050
|
+
statusCodeDistribution[m.status] = (statusCodeDistribution[m.status] ?? 0) + 1;
|
|
13051
|
+
}
|
|
13052
|
+
}
|
|
13053
|
+
const pathDistribution = {};
|
|
13054
|
+
const pathGroups = {};
|
|
13055
|
+
for (const m of this.metrics) {
|
|
13056
|
+
if (!pathGroups[m.path]) {
|
|
13057
|
+
pathGroups[m.path] = [];
|
|
13058
|
+
}
|
|
13059
|
+
pathGroups[m.path].push(m);
|
|
13060
|
+
}
|
|
13061
|
+
for (const [path, group] of Object.entries(pathGroups)) {
|
|
13062
|
+
const successCount = group.filter((m) => m.success).length;
|
|
13063
|
+
const avgDuration2 = group.reduce((sum, m) => sum + m.durationMs, 0) / group.length;
|
|
13064
|
+
pathDistribution[path] = {
|
|
13065
|
+
count: group.length,
|
|
13066
|
+
successCount,
|
|
13067
|
+
avgDurationMs: Math.round(avgDuration2)
|
|
13068
|
+
};
|
|
13069
|
+
}
|
|
13070
|
+
const totalRetries = this.metrics.reduce((sum, m) => sum + m.retryCount, 0);
|
|
13071
|
+
const avgDuration = this.metrics.reduce((sum, m) => sum + m.durationMs, 0) / this.metrics.length;
|
|
13072
|
+
return {
|
|
13073
|
+
totalRequests: this.metrics.length,
|
|
13074
|
+
successfulRequests: successful.length,
|
|
13075
|
+
failedRequests: this.metrics.length - successful.length,
|
|
13076
|
+
successRate: successful.length / this.metrics.length,
|
|
13077
|
+
avgDurationMs: Math.round(avgDuration),
|
|
13078
|
+
p50Ms: p(50),
|
|
13079
|
+
p90Ms: p(90),
|
|
13080
|
+
p95Ms: p(95),
|
|
13081
|
+
p99Ms: p(99),
|
|
13082
|
+
minDurationMs: durations[0],
|
|
13083
|
+
maxDurationMs: durations[durations.length - 1],
|
|
13084
|
+
totalRetries,
|
|
13085
|
+
statusCodeDistribution,
|
|
13086
|
+
pathDistribution
|
|
13087
|
+
};
|
|
13088
|
+
}
|
|
13089
|
+
getAll() {
|
|
13090
|
+
return [...this.metrics];
|
|
13091
|
+
}
|
|
13092
|
+
getByPath(path) {
|
|
13093
|
+
return this.metrics.filter((m) => m.path === path);
|
|
13094
|
+
}
|
|
13095
|
+
flush() {
|
|
13096
|
+
const result = [...this.metrics];
|
|
13097
|
+
this.metrics = [];
|
|
13098
|
+
return result;
|
|
13099
|
+
}
|
|
13100
|
+
clear() {
|
|
13101
|
+
this.metrics = [];
|
|
13102
|
+
}
|
|
13103
|
+
cleanup() {
|
|
13104
|
+
const now = Date.now();
|
|
13105
|
+
this.metrics = this.metrics.filter((m) => now - m.startTime < this.ttlMs);
|
|
13106
|
+
while (this.metrics.length > this.maxMetrics) {
|
|
13107
|
+
this.metrics.shift();
|
|
13108
|
+
}
|
|
13109
|
+
}
|
|
13110
|
+
emptySummary() {
|
|
13111
|
+
return {
|
|
13112
|
+
totalRequests: 0,
|
|
13113
|
+
successfulRequests: 0,
|
|
13114
|
+
failedRequests: 0,
|
|
13115
|
+
successRate: 0,
|
|
13116
|
+
avgDurationMs: 0,
|
|
13117
|
+
p50Ms: 0,
|
|
13118
|
+
p90Ms: 0,
|
|
13119
|
+
p95Ms: 0,
|
|
13120
|
+
p99Ms: 0,
|
|
13121
|
+
minDurationMs: 0,
|
|
13122
|
+
maxDurationMs: 0,
|
|
13123
|
+
totalRetries: 0,
|
|
13124
|
+
statusCodeDistribution: {},
|
|
13125
|
+
pathDistribution: {}
|
|
13126
|
+
};
|
|
13127
|
+
}
|
|
13128
|
+
};
|
|
13129
|
+
function createMetricsCollector(config) {
|
|
13130
|
+
return new DefaultMetricsCollector(config);
|
|
13131
|
+
}
|
|
13132
|
+
var MetricsRequestInterceptor = class {
|
|
13133
|
+
constructor() {
|
|
13134
|
+
this.name = "metrics-request";
|
|
13135
|
+
this.order = -1e3;
|
|
13136
|
+
}
|
|
13137
|
+
// 最先执行
|
|
13138
|
+
intercept(context) {
|
|
13139
|
+
return context.options;
|
|
13140
|
+
}
|
|
13141
|
+
};
|
|
13142
|
+
var MetricsResponseInterceptor = class {
|
|
13143
|
+
// 最后执行
|
|
13144
|
+
constructor(collector) {
|
|
13145
|
+
this.collector = collector;
|
|
13146
|
+
this.name = "metrics-response";
|
|
13147
|
+
this.order = 1e3;
|
|
13148
|
+
}
|
|
13149
|
+
intercept(response, context) {
|
|
13150
|
+
const endTime = Date.now();
|
|
13151
|
+
const metrics = {
|
|
13152
|
+
requestId: context.requestId,
|
|
13153
|
+
url: context.url,
|
|
13154
|
+
path: extractPath(context.url),
|
|
13155
|
+
method: context.options.method,
|
|
13156
|
+
startTime: context.startTime,
|
|
13157
|
+
endTime,
|
|
13158
|
+
durationMs: endTime - context.startTime,
|
|
13159
|
+
status: response.status,
|
|
13160
|
+
success: true,
|
|
13161
|
+
retryCount: context.retryCount,
|
|
13162
|
+
traceId: context.traceId
|
|
13163
|
+
};
|
|
13164
|
+
this.collector.record(metrics);
|
|
13165
|
+
return response;
|
|
13166
|
+
}
|
|
13167
|
+
};
|
|
13168
|
+
var MetricsErrorInterceptor = class {
|
|
13169
|
+
// 最后执行
|
|
13170
|
+
constructor(collector) {
|
|
13171
|
+
this.collector = collector;
|
|
13172
|
+
this.name = "metrics-error";
|
|
13173
|
+
this.order = 1e3;
|
|
13174
|
+
}
|
|
13175
|
+
intercept(error, context) {
|
|
13176
|
+
const endTime = Date.now();
|
|
13177
|
+
const metrics = {
|
|
13178
|
+
requestId: context.requestId,
|
|
13179
|
+
url: context.url,
|
|
13180
|
+
path: extractPath(context.url),
|
|
13181
|
+
method: context.options.method,
|
|
13182
|
+
startTime: context.startTime,
|
|
13183
|
+
endTime,
|
|
13184
|
+
durationMs: endTime - context.startTime,
|
|
13185
|
+
success: false,
|
|
13186
|
+
retryCount: context.retryCount,
|
|
13187
|
+
traceId: context.traceId,
|
|
13188
|
+
error: error.message
|
|
13189
|
+
};
|
|
13190
|
+
this.collector.record(metrics);
|
|
13191
|
+
return void 0;
|
|
13192
|
+
}
|
|
13193
|
+
};
|
|
13194
|
+
function createMetricsInterceptors(collector) {
|
|
13195
|
+
return {
|
|
13196
|
+
request: new MetricsRequestInterceptor(),
|
|
13197
|
+
response: new MetricsResponseInterceptor(collector),
|
|
13198
|
+
error: new MetricsErrorInterceptor(collector)
|
|
13199
|
+
};
|
|
13200
|
+
}
|
|
13201
|
+
|
|
13202
|
+
// src/plugins/logger.ts
|
|
13203
|
+
var ConsoleLogger = class {
|
|
13204
|
+
constructor(config = {}) {
|
|
13205
|
+
this.prefix = config.prefix ?? "[DJV-API]";
|
|
13206
|
+
this.level = config.level ?? "info";
|
|
13207
|
+
this.timestamp = config.timestamp ?? true;
|
|
13208
|
+
this.levelPriority = LOG_LEVEL_PRIORITY[this.level];
|
|
13209
|
+
}
|
|
13210
|
+
debug(message, ...args) {
|
|
13211
|
+
this.log("debug", message, args);
|
|
13212
|
+
}
|
|
13213
|
+
info(message, ...args) {
|
|
13214
|
+
this.log("info", message, args);
|
|
13215
|
+
}
|
|
13216
|
+
warn(message, ...args) {
|
|
13217
|
+
this.log("warn", message, args);
|
|
13218
|
+
}
|
|
13219
|
+
error(message, ...args) {
|
|
13220
|
+
this.log("error", message, args);
|
|
13221
|
+
}
|
|
13222
|
+
log(level, message, args) {
|
|
13223
|
+
if (LOG_LEVEL_PRIORITY[level] < this.levelPriority) {
|
|
13224
|
+
return;
|
|
13225
|
+
}
|
|
13226
|
+
const formattedMessage = this.formatMessage(level, message);
|
|
13227
|
+
switch (level) {
|
|
13228
|
+
case "debug":
|
|
13229
|
+
console.debug(formattedMessage, ...args);
|
|
13230
|
+
break;
|
|
13231
|
+
case "info":
|
|
13232
|
+
console.info(formattedMessage, ...args);
|
|
13233
|
+
break;
|
|
13234
|
+
case "warn":
|
|
13235
|
+
console.warn(formattedMessage, ...args);
|
|
13236
|
+
break;
|
|
13237
|
+
case "error":
|
|
13238
|
+
console.error(formattedMessage, ...args);
|
|
13239
|
+
break;
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
formatMessage(level, message) {
|
|
13243
|
+
const parts = [];
|
|
13244
|
+
if (this.timestamp) {
|
|
13245
|
+
parts.push(`[${(/* @__PURE__ */ new Date()).toISOString()}]`);
|
|
13246
|
+
}
|
|
13247
|
+
parts.push(this.prefix);
|
|
13248
|
+
parts.push(`[${level.toUpperCase()}]`);
|
|
13249
|
+
parts.push(message);
|
|
13250
|
+
return parts.join(" ");
|
|
13251
|
+
}
|
|
13252
|
+
};
|
|
13253
|
+
function createConsoleLogger(config) {
|
|
13254
|
+
return new ConsoleLogger(config);
|
|
13255
|
+
}
|
|
13256
|
+
var SilentLogger = class {
|
|
13257
|
+
debug(_message, ..._args) {
|
|
13258
|
+
}
|
|
13259
|
+
info(_message, ..._args) {
|
|
13260
|
+
}
|
|
13261
|
+
warn(_message, ..._args) {
|
|
13262
|
+
}
|
|
13263
|
+
error(_message, ..._args) {
|
|
13264
|
+
}
|
|
13265
|
+
};
|
|
13266
|
+
function createSilentLogger() {
|
|
13267
|
+
return new SilentLogger();
|
|
13268
|
+
}
|
|
13269
|
+
var silentLogger = new SilentLogger();
|
|
13270
|
+
var BufferLogger = class {
|
|
13271
|
+
constructor(maxSize = 1e3) {
|
|
13272
|
+
this.buffer = [];
|
|
13273
|
+
this.maxSize = maxSize;
|
|
13274
|
+
}
|
|
13275
|
+
debug(message, ...args) {
|
|
13276
|
+
this.add("debug", message, args);
|
|
13277
|
+
}
|
|
13278
|
+
info(message, ...args) {
|
|
13279
|
+
this.add("info", message, args);
|
|
13280
|
+
}
|
|
13281
|
+
warn(message, ...args) {
|
|
13282
|
+
this.add("warn", message, args);
|
|
13283
|
+
}
|
|
13284
|
+
error(message, ...args) {
|
|
13285
|
+
this.add("error", message, args);
|
|
13286
|
+
}
|
|
13287
|
+
/**
|
|
13288
|
+
* 获取所有日志
|
|
13289
|
+
*/
|
|
13290
|
+
getLogs() {
|
|
13291
|
+
return [...this.buffer];
|
|
13292
|
+
}
|
|
13293
|
+
/**
|
|
13294
|
+
* 获取指定级别的日志
|
|
13295
|
+
*/
|
|
13296
|
+
getLogsByLevel(level) {
|
|
13297
|
+
return this.buffer.filter((log) => log.level === level);
|
|
13298
|
+
}
|
|
13299
|
+
/**
|
|
13300
|
+
* 清空日志
|
|
13301
|
+
*/
|
|
13302
|
+
clear() {
|
|
13303
|
+
this.buffer.length = 0;
|
|
13304
|
+
}
|
|
13305
|
+
/**
|
|
13306
|
+
* 获取日志数量
|
|
13307
|
+
*/
|
|
13308
|
+
get size() {
|
|
13309
|
+
return this.buffer.length;
|
|
13310
|
+
}
|
|
13311
|
+
add(level, message, args) {
|
|
13312
|
+
this.buffer.push({
|
|
13313
|
+
level,
|
|
13314
|
+
message,
|
|
13315
|
+
args,
|
|
13316
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
13317
|
+
});
|
|
13318
|
+
while (this.buffer.length > this.maxSize) {
|
|
13319
|
+
this.buffer.shift();
|
|
13320
|
+
}
|
|
13321
|
+
}
|
|
13322
|
+
};
|
|
13323
|
+
function createBufferLogger(maxSize) {
|
|
13324
|
+
return new BufferLogger(maxSize);
|
|
13325
|
+
}
|
|
13326
|
+
|
|
13327
|
+
// src/utils/headers.ts
|
|
13328
|
+
var DEFAULT_HEADERS = {
|
|
13329
|
+
"Content-Type": "application/json",
|
|
13330
|
+
Accept: "application/json"
|
|
13331
|
+
};
|
|
13332
|
+
function mergeHeaders(...headersList) {
|
|
13333
|
+
const result = {};
|
|
13334
|
+
const lowercaseKeyMap = {};
|
|
13335
|
+
for (const headers of headersList) {
|
|
13336
|
+
if (!headers) continue;
|
|
13337
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
13338
|
+
const lowercaseKey = key.toLowerCase();
|
|
13339
|
+
if (lowercaseKeyMap[lowercaseKey]) {
|
|
13340
|
+
delete result[lowercaseKeyMap[lowercaseKey]];
|
|
13341
|
+
}
|
|
13342
|
+
result[key] = value;
|
|
13343
|
+
lowercaseKeyMap[lowercaseKey] = key;
|
|
13344
|
+
}
|
|
13345
|
+
}
|
|
13346
|
+
return result;
|
|
13347
|
+
}
|
|
13348
|
+
function getHeader(headers, name) {
|
|
13349
|
+
const lowercaseName = name.toLowerCase();
|
|
13350
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
13351
|
+
if (key.toLowerCase() === lowercaseName) {
|
|
13352
|
+
return value;
|
|
13353
|
+
}
|
|
13354
|
+
}
|
|
13355
|
+
return void 0;
|
|
13356
|
+
}
|
|
13357
|
+
function setHeader(headers, name, value) {
|
|
13358
|
+
const lowercaseName = name.toLowerCase();
|
|
13359
|
+
for (const key of Object.keys(headers)) {
|
|
13360
|
+
if (key.toLowerCase() === lowercaseName) {
|
|
13361
|
+
delete headers[key];
|
|
13362
|
+
}
|
|
13363
|
+
}
|
|
13364
|
+
headers[name] = value;
|
|
13365
|
+
}
|
|
13366
|
+
function removeHeader(headers, name) {
|
|
13367
|
+
const lowercaseName = name.toLowerCase();
|
|
13368
|
+
let removed = false;
|
|
13369
|
+
for (const key of Object.keys(headers)) {
|
|
13370
|
+
if (key.toLowerCase() === lowercaseName) {
|
|
13371
|
+
delete headers[key];
|
|
13372
|
+
removed = true;
|
|
13373
|
+
}
|
|
13374
|
+
}
|
|
13375
|
+
return removed;
|
|
13376
|
+
}
|
|
13377
|
+
function extractResponseHeaders(response) {
|
|
13378
|
+
const headers = {};
|
|
13379
|
+
response.headers.forEach((value, key) => {
|
|
13380
|
+
headers[key] = value;
|
|
13381
|
+
});
|
|
13382
|
+
return headers;
|
|
13383
|
+
}
|
|
13384
|
+
function hasContentType(headers, contentType) {
|
|
13385
|
+
const value = getHeader(headers, "Content-Type");
|
|
13386
|
+
if (!value) return false;
|
|
13387
|
+
return value.toLowerCase().includes(contentType.toLowerCase());
|
|
13388
|
+
}
|
|
13389
|
+
|
|
13390
|
+
// src/clients/base-client.ts
|
|
13391
|
+
var BaseClient = class {
|
|
13392
|
+
constructor(config) {
|
|
13393
|
+
this.config = {
|
|
13394
|
+
timeout: 3e4,
|
|
13395
|
+
...config
|
|
13396
|
+
};
|
|
13397
|
+
this.interceptorManager = new InterceptorManager();
|
|
13398
|
+
this.authenticator = config.auth ? createAuthenticatorFromConfig(config.auth) : noAuthenticator;
|
|
13399
|
+
this.registerConfigInterceptors();
|
|
13400
|
+
}
|
|
13401
|
+
/**
|
|
13402
|
+
* GET 请求
|
|
13403
|
+
*/
|
|
13404
|
+
async get(path, query) {
|
|
13405
|
+
return this.request({ method: "GET", path, query });
|
|
13406
|
+
}
|
|
13407
|
+
/**
|
|
13408
|
+
* POST 请求
|
|
13409
|
+
*/
|
|
13410
|
+
async post(path, body, query) {
|
|
13411
|
+
return this.request({ method: "POST", path, body, query });
|
|
13412
|
+
}
|
|
13413
|
+
/**
|
|
13414
|
+
* PUT 请求
|
|
13415
|
+
*/
|
|
13416
|
+
async put(path, body) {
|
|
13417
|
+
return this.request({ method: "PUT", path, body });
|
|
13418
|
+
}
|
|
13419
|
+
/**
|
|
13420
|
+
* PATCH 请求
|
|
13421
|
+
*/
|
|
13422
|
+
async patch(path, body) {
|
|
13423
|
+
return this.request({ method: "PATCH", path, body });
|
|
13424
|
+
}
|
|
13425
|
+
/**
|
|
13426
|
+
* DELETE 请求
|
|
13427
|
+
*/
|
|
13428
|
+
async delete(path) {
|
|
13429
|
+
return this.request({ method: "DELETE", path });
|
|
13430
|
+
}
|
|
13431
|
+
/**
|
|
13432
|
+
* 获取拦截器管理器
|
|
13433
|
+
*/
|
|
13434
|
+
get interceptors() {
|
|
13435
|
+
return this.interceptorManager;
|
|
13436
|
+
}
|
|
13437
|
+
/**
|
|
13438
|
+
* 创建请求上下文
|
|
13439
|
+
*/
|
|
13440
|
+
createRequestContext(options) {
|
|
13441
|
+
const requestId = generateRequestId();
|
|
13442
|
+
const traceId = generateTraceId();
|
|
13443
|
+
const url2 = buildUrl(this.config.baseUrl, options.path, options.query);
|
|
13444
|
+
const headers = mergeHeaders(
|
|
13445
|
+
DEFAULT_HEADERS,
|
|
13446
|
+
this.config.headers,
|
|
13447
|
+
options.headers
|
|
13448
|
+
);
|
|
13449
|
+
const mutableOptions = {
|
|
13450
|
+
method: options.method,
|
|
13451
|
+
path: options.path,
|
|
13452
|
+
query: options.query ? { ...options.query } : void 0,
|
|
13453
|
+
body: options.body,
|
|
13454
|
+
headers,
|
|
13455
|
+
timeout: options.timeout ?? this.config.timeout,
|
|
13456
|
+
skipAuth: options.skipAuth ?? false,
|
|
13457
|
+
signal: options.signal,
|
|
13458
|
+
metadata: options.metadata ? { ...options.metadata } : {}
|
|
13459
|
+
};
|
|
13460
|
+
return {
|
|
13461
|
+
requestId,
|
|
13462
|
+
traceId,
|
|
13463
|
+
startTime: Date.now(),
|
|
13464
|
+
retryCount: 0,
|
|
13465
|
+
originalOptions: options,
|
|
13466
|
+
options: mutableOptions,
|
|
13467
|
+
url: url2,
|
|
13468
|
+
metadata: {}
|
|
13469
|
+
};
|
|
13470
|
+
}
|
|
13471
|
+
/**
|
|
13472
|
+
* 执行请求拦截器
|
|
13473
|
+
*/
|
|
13474
|
+
async runRequestInterceptors(context) {
|
|
13475
|
+
if (!context.options.skipAuth) {
|
|
13476
|
+
await this.authenticator.authenticate(context.options.headers);
|
|
13477
|
+
}
|
|
13478
|
+
return this.interceptorManager.executeRequestInterceptors(context);
|
|
13479
|
+
}
|
|
13480
|
+
/**
|
|
13481
|
+
* 执行响应拦截器
|
|
13482
|
+
*/
|
|
13483
|
+
async runResponseInterceptors(response, context) {
|
|
13484
|
+
return this.interceptorManager.executeResponseInterceptors(response, context);
|
|
13485
|
+
}
|
|
13486
|
+
/**
|
|
13487
|
+
* 执行错误拦截器
|
|
13488
|
+
*/
|
|
13489
|
+
async runErrorInterceptors(error, context) {
|
|
13490
|
+
return this.interceptorManager.executeErrorInterceptors(error, context);
|
|
13491
|
+
}
|
|
13492
|
+
/**
|
|
13493
|
+
* 注册配置中的拦截器
|
|
13494
|
+
*/
|
|
13495
|
+
registerConfigInterceptors() {
|
|
13496
|
+
if (this.config.requestInterceptors) {
|
|
13497
|
+
for (const interceptor of this.config.requestInterceptors) {
|
|
13498
|
+
this.interceptorManager.addRequestInterceptor(interceptor);
|
|
13499
|
+
}
|
|
13500
|
+
}
|
|
13501
|
+
if (this.config.responseInterceptors) {
|
|
13502
|
+
for (const interceptor of this.config.responseInterceptors) {
|
|
13503
|
+
this.interceptorManager.addResponseInterceptor(interceptor);
|
|
13504
|
+
}
|
|
13505
|
+
}
|
|
13506
|
+
if (this.config.errorInterceptors) {
|
|
13507
|
+
for (const interceptor of this.config.errorInterceptors) {
|
|
13508
|
+
this.interceptorManager.addErrorInterceptor(interceptor);
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
}
|
|
13512
|
+
};
|
|
13513
|
+
|
|
13514
|
+
// src/clients/fetch-client.ts
|
|
13515
|
+
var FetchClient = class extends BaseClient {
|
|
13516
|
+
constructor(config) {
|
|
13517
|
+
super(config);
|
|
13518
|
+
this.enableRetry = config.enableRetry !== false;
|
|
13519
|
+
}
|
|
13520
|
+
/**
|
|
13521
|
+
* 发起请求
|
|
13522
|
+
*/
|
|
13523
|
+
async request(options) {
|
|
13524
|
+
const context = this.createRequestContext(options);
|
|
13525
|
+
try {
|
|
13526
|
+
const response = await this.executeWithRetry(context);
|
|
13527
|
+
return response.data;
|
|
13528
|
+
} catch (error) {
|
|
13529
|
+
const recovered = await this.runErrorInterceptors(error, context);
|
|
13530
|
+
if (recovered) {
|
|
13531
|
+
return recovered.data;
|
|
13532
|
+
}
|
|
13533
|
+
throw error;
|
|
13534
|
+
}
|
|
13535
|
+
}
|
|
13536
|
+
/**
|
|
13537
|
+
* 带重试的请求执行
|
|
13538
|
+
*/
|
|
13539
|
+
async executeWithRetry(context) {
|
|
13540
|
+
const retryConfig = this.config.retry;
|
|
13541
|
+
const maxRetries = this.enableRetry && retryConfig ? retryConfig.maxRetries : 0;
|
|
13542
|
+
for (; ; ) {
|
|
13543
|
+
try {
|
|
13544
|
+
return await this.executeRequest(context);
|
|
13545
|
+
} catch (error) {
|
|
13546
|
+
const err = error;
|
|
13547
|
+
if (!this.shouldRetry(err, context, retryConfig, maxRetries)) {
|
|
13548
|
+
throw err;
|
|
13549
|
+
}
|
|
13550
|
+
const retryAfterSeconds = err instanceof ApiError ? err.getRetryAfter() : void 0;
|
|
13551
|
+
let delayMs = calculateRetryDelay(retryConfig, context.retryCount);
|
|
13552
|
+
if (retryConfig?.respectRetryAfter !== false && retryAfterSeconds !== void 0) {
|
|
13553
|
+
delayMs = Math.min(retryAfterSeconds * 1e3, retryConfig.maxDelayMs);
|
|
13554
|
+
}
|
|
13555
|
+
if (retryConfig?.onRetry) {
|
|
13556
|
+
retryConfig.onRetry({
|
|
13557
|
+
attempt: context.retryCount + 1,
|
|
13558
|
+
maxRetries: retryConfig.maxRetries,
|
|
13559
|
+
delayMs,
|
|
13560
|
+
error: err,
|
|
13561
|
+
url: context.url,
|
|
13562
|
+
method: context.options.method
|
|
13563
|
+
});
|
|
13564
|
+
}
|
|
13565
|
+
this.logDebug(
|
|
13566
|
+
`[Retry] ${context.options.method} ${context.options.path} - attempt ${context.retryCount + 1}/${maxRetries}, delay ${delayMs}ms`
|
|
13567
|
+
);
|
|
13568
|
+
await sleep(delayMs);
|
|
13569
|
+
context.retryCount++;
|
|
13570
|
+
}
|
|
13571
|
+
}
|
|
13572
|
+
}
|
|
13573
|
+
/**
|
|
13574
|
+
* 判断是否应该重试
|
|
13575
|
+
*/
|
|
13576
|
+
shouldRetry(error, context, retryConfig, maxRetries) {
|
|
13577
|
+
if (!this.enableRetry || !retryConfig) {
|
|
13578
|
+
return false;
|
|
13579
|
+
}
|
|
13580
|
+
if (context.retryCount >= maxRetries) {
|
|
13581
|
+
return false;
|
|
13582
|
+
}
|
|
13583
|
+
if (error instanceof AbortError) {
|
|
13584
|
+
return false;
|
|
13585
|
+
}
|
|
13586
|
+
if (retryConfig.shouldRetry) {
|
|
13587
|
+
const statusCode = error instanceof ApiError ? error.statusCode : void 0;
|
|
13588
|
+
const retryAfter = error instanceof ApiError ? error.getRetryAfter() : void 0;
|
|
13589
|
+
return retryConfig.shouldRetry(error, context.retryCount, {
|
|
13590
|
+
url: context.url,
|
|
13591
|
+
method: context.options.method,
|
|
13592
|
+
statusCode,
|
|
13593
|
+
retryAfter
|
|
13594
|
+
});
|
|
13595
|
+
}
|
|
13596
|
+
if (error instanceof NetworkError) {
|
|
13597
|
+
return retryConfig.retryOnNetworkError !== false;
|
|
13598
|
+
}
|
|
13599
|
+
if (error instanceof TimeoutError) {
|
|
13600
|
+
return retryConfig.retryOnTimeout !== false;
|
|
13601
|
+
}
|
|
13602
|
+
if (error instanceof ApiError) {
|
|
13603
|
+
const retryableCodes = retryConfig.retryableStatusCodes ?? DEFAULT_RETRY_CONFIG.retryableStatusCodes;
|
|
13604
|
+
return retryableCodes?.includes(error.statusCode) ?? false;
|
|
13605
|
+
}
|
|
13606
|
+
return false;
|
|
13607
|
+
}
|
|
13608
|
+
/**
|
|
13609
|
+
* 执行单次请求
|
|
13610
|
+
*/
|
|
13611
|
+
async executeRequest(context) {
|
|
13612
|
+
const options = await this.runRequestInterceptors(context);
|
|
13613
|
+
context.options = options;
|
|
13614
|
+
const url2 = this.buildFullUrl(context);
|
|
13615
|
+
const init = this.buildFetchInit(context);
|
|
13616
|
+
const { signal, cleanup } = this.createTimeoutSignal(
|
|
13617
|
+
options.timeout ?? 3e4,
|
|
13618
|
+
options.signal
|
|
13619
|
+
);
|
|
13620
|
+
this.logDebug(`[Request] ${options.method} ${url2}`, {
|
|
13621
|
+
requestId: context.requestId,
|
|
13622
|
+
headers: options.headers
|
|
13623
|
+
});
|
|
13624
|
+
try {
|
|
13625
|
+
const fetchResponse = await fetch(url2, {
|
|
13626
|
+
...init,
|
|
13627
|
+
signal
|
|
13628
|
+
});
|
|
13629
|
+
const response = await this.parseResponse(fetchResponse, context);
|
|
13630
|
+
const duration = Date.now() - context.startTime;
|
|
13631
|
+
this.logDebug(
|
|
13632
|
+
`[Response] ${options.method} ${options.path} - ${fetchResponse.status} (${duration}ms)`,
|
|
13633
|
+
{ requestId: context.requestId, status: fetchResponse.status, duration }
|
|
13634
|
+
);
|
|
13635
|
+
return this.runResponseInterceptors(response, context);
|
|
13636
|
+
} catch (error) {
|
|
13637
|
+
const transformedError = this.transformError(error, context);
|
|
13638
|
+
const duration = Date.now() - context.startTime;
|
|
13639
|
+
this.logError(
|
|
13640
|
+
`[Error] ${options.method} ${options.path} - ${transformedError.message} (${duration}ms)`,
|
|
13641
|
+
{ requestId: context.requestId, error: transformedError.message, duration }
|
|
13642
|
+
);
|
|
13643
|
+
throw transformedError;
|
|
13644
|
+
} finally {
|
|
13645
|
+
cleanup();
|
|
13646
|
+
}
|
|
13647
|
+
}
|
|
13648
|
+
/**
|
|
13649
|
+
* 构建完整 URL
|
|
13650
|
+
*/
|
|
13651
|
+
buildFullUrl(context) {
|
|
13652
|
+
const { options } = context;
|
|
13653
|
+
const base = this.config.baseUrl.replace(/\/+$/, "");
|
|
13654
|
+
const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
|
|
13655
|
+
const url2 = new URL(`${base}${path}`);
|
|
13656
|
+
if (options.query) {
|
|
13657
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
13658
|
+
if (value !== void 0 && value !== null) {
|
|
13659
|
+
url2.searchParams.set(key, String(value));
|
|
13660
|
+
}
|
|
13661
|
+
}
|
|
13662
|
+
}
|
|
13663
|
+
return url2.toString();
|
|
13664
|
+
}
|
|
13665
|
+
/**
|
|
13666
|
+
* 构建 fetch init 对象
|
|
13667
|
+
*/
|
|
13668
|
+
buildFetchInit(context) {
|
|
13669
|
+
const { options } = context;
|
|
13670
|
+
const init = {
|
|
13671
|
+
method: options.method,
|
|
13672
|
+
headers: options.headers
|
|
13673
|
+
};
|
|
13674
|
+
if (options.body !== void 0) {
|
|
13675
|
+
init.body = JSON.stringify(options.body);
|
|
13676
|
+
}
|
|
13677
|
+
return init;
|
|
13678
|
+
}
|
|
13679
|
+
/**
|
|
13680
|
+
* 创建超时信号
|
|
13681
|
+
*/
|
|
13682
|
+
createTimeoutSignal(timeoutMs, externalSignal) {
|
|
13683
|
+
const controller = new AbortController();
|
|
13684
|
+
const timeoutId = setTimeout(() => {
|
|
13685
|
+
controller.abort(new DOMException(`Timeout of ${timeoutMs}ms exceeded`, "TimeoutError"));
|
|
13686
|
+
}, timeoutMs);
|
|
13687
|
+
if (externalSignal) {
|
|
13688
|
+
if (externalSignal.aborted) {
|
|
13689
|
+
controller.abort(externalSignal.reason);
|
|
13690
|
+
} else {
|
|
13691
|
+
const onAbort = () => {
|
|
13692
|
+
controller.abort(externalSignal.reason);
|
|
13693
|
+
};
|
|
13694
|
+
externalSignal.addEventListener("abort", onAbort, { once: true });
|
|
13695
|
+
}
|
|
13696
|
+
}
|
|
13697
|
+
return {
|
|
13698
|
+
signal: controller.signal,
|
|
13699
|
+
cleanup: () => {
|
|
13700
|
+
if (timeoutId !== void 0) {
|
|
13701
|
+
clearTimeout(timeoutId);
|
|
13702
|
+
}
|
|
13703
|
+
}
|
|
13704
|
+
};
|
|
13705
|
+
}
|
|
13706
|
+
/**
|
|
13707
|
+
* 解析响应
|
|
13708
|
+
*/
|
|
13709
|
+
async parseResponse(fetchResponse, context) {
|
|
13710
|
+
const headers = extractResponseHeaders(fetchResponse);
|
|
13711
|
+
let data;
|
|
13712
|
+
try {
|
|
13713
|
+
data = await fetchResponse.json();
|
|
13714
|
+
} catch {
|
|
13715
|
+
if (!fetchResponse.ok) {
|
|
13716
|
+
throw ApiError.fromResponse(
|
|
13717
|
+
{
|
|
13718
|
+
code: `HTTP_${fetchResponse.status}`,
|
|
13719
|
+
message: fetchResponse.statusText || `HTTP Error ${fetchResponse.status}`,
|
|
13720
|
+
requestId: context.requestId
|
|
13721
|
+
},
|
|
13722
|
+
fetchResponse.status,
|
|
13723
|
+
parseRetryAfter(getHeader(headers, "Retry-After"))
|
|
13724
|
+
);
|
|
13725
|
+
}
|
|
13726
|
+
data = {
|
|
13727
|
+
success: true,
|
|
13728
|
+
data: void 0,
|
|
13729
|
+
requestId: context.requestId
|
|
13730
|
+
};
|
|
13731
|
+
}
|
|
13732
|
+
if (!fetchResponse.ok || data.success === false) {
|
|
13733
|
+
throw ApiError.fromResponse(
|
|
13734
|
+
{
|
|
13735
|
+
code: data.code,
|
|
13736
|
+
message: data.message,
|
|
13737
|
+
details: data.details,
|
|
13738
|
+
requestId: data.requestId ?? context.requestId,
|
|
13739
|
+
traceId: data.traceId ?? context.traceId
|
|
13740
|
+
},
|
|
13741
|
+
fetchResponse.status,
|
|
13742
|
+
parseRetryAfter(getHeader(headers, "Retry-After"))
|
|
13743
|
+
);
|
|
13744
|
+
}
|
|
13745
|
+
return {
|
|
13746
|
+
data: data.data,
|
|
13747
|
+
status: fetchResponse.status,
|
|
13748
|
+
statusText: fetchResponse.statusText,
|
|
13749
|
+
headers,
|
|
13750
|
+
requestId: data.requestId ?? context.requestId,
|
|
13751
|
+
traceId: data.traceId ?? context.traceId
|
|
13752
|
+
};
|
|
13753
|
+
}
|
|
13754
|
+
/**
|
|
13755
|
+
* 转换错误
|
|
13756
|
+
*/
|
|
13757
|
+
transformError(error, _context) {
|
|
13758
|
+
if (error instanceof ApiError || error instanceof NetworkError || error instanceof TimeoutError || error instanceof AbortError) {
|
|
13759
|
+
return error;
|
|
13760
|
+
}
|
|
13761
|
+
if (error.name === "AbortError") {
|
|
13762
|
+
if (error.message?.includes("Timeout")) {
|
|
13763
|
+
const match = error.message.match(/(\d+)ms/);
|
|
13764
|
+
const timeout = match ? parseInt(match[1], 10) : 3e4;
|
|
13765
|
+
return new TimeoutError(timeout);
|
|
13766
|
+
}
|
|
13767
|
+
return AbortError.fromNative(error);
|
|
13768
|
+
}
|
|
13769
|
+
if (error.name === "TypeError") {
|
|
13770
|
+
return NetworkError.fromFetchError(error);
|
|
11718
13771
|
}
|
|
11719
|
-
|
|
11720
|
-
return retryableCodes.includes(code);
|
|
13772
|
+
return new NetworkError(error.message, error);
|
|
11721
13773
|
}
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
13774
|
+
// ============================================================
|
|
13775
|
+
// 日志辅助方法
|
|
13776
|
+
// ============================================================
|
|
13777
|
+
/**
|
|
13778
|
+
* 调试日志
|
|
13779
|
+
*/
|
|
13780
|
+
logDebug(message, data) {
|
|
13781
|
+
if (this.config.debug && this.config.logger) {
|
|
13782
|
+
this.config.logger.debug(message, data);
|
|
13783
|
+
}
|
|
11729
13784
|
}
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
this.
|
|
11735
|
-
|
|
13785
|
+
/**
|
|
13786
|
+
* 错误日志
|
|
13787
|
+
*/
|
|
13788
|
+
logError(message, data) {
|
|
13789
|
+
if (this.config.logger) {
|
|
13790
|
+
this.config.logger.error(message, data);
|
|
13791
|
+
}
|
|
11736
13792
|
}
|
|
11737
13793
|
};
|
|
13794
|
+
function createFetchClient(config) {
|
|
13795
|
+
return new FetchClient(config);
|
|
13796
|
+
}
|
|
11738
13797
|
|
|
11739
13798
|
// ../../node_modules/.pnpm/axios@1.13.2/node_modules/axios/lib/helpers/bind.js
|
|
11740
13799
|
function bind(fn, thisArg) {
|
|
@@ -12391,7 +14450,7 @@ function buildURL(url2, params, options) {
|
|
|
12391
14450
|
}
|
|
12392
14451
|
|
|
12393
14452
|
// ../../node_modules/.pnpm/axios@1.13.2/node_modules/axios/lib/core/InterceptorManager.js
|
|
12394
|
-
var
|
|
14453
|
+
var InterceptorManager2 = class {
|
|
12395
14454
|
constructor() {
|
|
12396
14455
|
this.handlers = [];
|
|
12397
14456
|
}
|
|
@@ -12452,7 +14511,7 @@ var InterceptorManager = class {
|
|
|
12452
14511
|
});
|
|
12453
14512
|
}
|
|
12454
14513
|
};
|
|
12455
|
-
var InterceptorManager_default =
|
|
14514
|
+
var InterceptorManager_default = InterceptorManager2;
|
|
12456
14515
|
|
|
12457
14516
|
// ../../node_modules/.pnpm/axios@1.13.2/node_modules/axios/lib/defaults/transitional.js
|
|
12458
14517
|
var transitional_default = {
|
|
@@ -12801,7 +14860,7 @@ var AxiosHeaders = class {
|
|
|
12801
14860
|
}
|
|
12802
14861
|
set(header, valueOrRewrite, rewrite) {
|
|
12803
14862
|
const self2 = this;
|
|
12804
|
-
function
|
|
14863
|
+
function setHeader2(_value, _header, _rewrite) {
|
|
12805
14864
|
const lHeader = normalizeHeader(_header);
|
|
12806
14865
|
if (!lHeader) {
|
|
12807
14866
|
throw new Error("header name must be a non-empty string");
|
|
@@ -12811,7 +14870,7 @@ var AxiosHeaders = class {
|
|
|
12811
14870
|
self2[key || _header] = normalizeValue(_value);
|
|
12812
14871
|
}
|
|
12813
14872
|
}
|
|
12814
|
-
const setHeaders = (headers, _rewrite) => utils_default.forEach(headers, (_value, _header) =>
|
|
14873
|
+
const setHeaders = (headers, _rewrite) => utils_default.forEach(headers, (_value, _header) => setHeader2(_value, _header, _rewrite));
|
|
12815
14874
|
if (utils_default.isPlainObject(header) || header instanceof this.constructor) {
|
|
12816
14875
|
setHeaders(header, valueOrRewrite);
|
|
12817
14876
|
} else if (utils_default.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) {
|
|
@@ -12826,7 +14885,7 @@ var AxiosHeaders = class {
|
|
|
12826
14885
|
}
|
|
12827
14886
|
setHeaders(obj, valueOrRewrite);
|
|
12828
14887
|
} else {
|
|
12829
|
-
header != null &&
|
|
14888
|
+
header != null && setHeader2(valueOrRewrite, header, rewrite);
|
|
12830
14889
|
}
|
|
12831
14890
|
return this;
|
|
12832
14891
|
}
|
|
@@ -14585,7 +16644,7 @@ var factory = (env2) => {
|
|
|
14585
16644
|
const encodeText = isFetchSupported && (typeof TextEncoder2 === "function" ? /* @__PURE__ */ ((encoder) => (str) => encoder.encode(str))(new TextEncoder2()) : async (str) => new Uint8Array(await new Request(str).arrayBuffer()));
|
|
14586
16645
|
const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => {
|
|
14587
16646
|
let duplexAccessed = false;
|
|
14588
|
-
const
|
|
16647
|
+
const hasContentType2 = new Request(platform_default.origin, {
|
|
14589
16648
|
body: new ReadableStream2(),
|
|
14590
16649
|
method: "POST",
|
|
14591
16650
|
get duplex() {
|
|
@@ -14593,7 +16652,7 @@ var factory = (env2) => {
|
|
|
14593
16652
|
return "half";
|
|
14594
16653
|
}
|
|
14595
16654
|
}).headers.has("Content-Type");
|
|
14596
|
-
return duplexAccessed && !
|
|
16655
|
+
return duplexAccessed && !hasContentType2;
|
|
14597
16656
|
});
|
|
14598
16657
|
const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils_default.isReadableStream(new Response("").body));
|
|
14599
16658
|
const resolvers = {
|
|
@@ -15347,113 +17406,7 @@ var {
|
|
|
15347
17406
|
mergeConfig: mergeConfig2
|
|
15348
17407
|
} = axios_default;
|
|
15349
17408
|
|
|
15350
|
-
// src/
|
|
15351
|
-
async function addAuthToAxiosConfig(config, auth) {
|
|
15352
|
-
switch (auth.type) {
|
|
15353
|
-
case "bearer": {
|
|
15354
|
-
if (auth.getToken) {
|
|
15355
|
-
const token = typeof auth.getToken === "function" ? await auth.getToken() : auth.getToken;
|
|
15356
|
-
if (token) {
|
|
15357
|
-
config.headers.set("Authorization", `Bearer ${token}`);
|
|
15358
|
-
}
|
|
15359
|
-
}
|
|
15360
|
-
break;
|
|
15361
|
-
}
|
|
15362
|
-
case "api-key": {
|
|
15363
|
-
if (auth.apiKey) {
|
|
15364
|
-
const headerName = auth.apiKeyHeader ?? "X-API-Key";
|
|
15365
|
-
config.headers.set(headerName, auth.apiKey);
|
|
15366
|
-
}
|
|
15367
|
-
break;
|
|
15368
|
-
}
|
|
15369
|
-
case "basic": {
|
|
15370
|
-
if (auth.username && auth.password) {
|
|
15371
|
-
const encoded = btoa(`${auth.username}:${auth.password}`);
|
|
15372
|
-
config.headers.set("Authorization", `Basic ${encoded}`);
|
|
15373
|
-
}
|
|
15374
|
-
break;
|
|
15375
|
-
}
|
|
15376
|
-
case "custom": {
|
|
15377
|
-
if (auth.customAuth) {
|
|
15378
|
-
const headers = {};
|
|
15379
|
-
await auth.customAuth(headers);
|
|
15380
|
-
Object.entries(headers).forEach(([key, value]) => {
|
|
15381
|
-
config.headers.set(key, value);
|
|
15382
|
-
});
|
|
15383
|
-
}
|
|
15384
|
-
break;
|
|
15385
|
-
}
|
|
15386
|
-
}
|
|
15387
|
-
}
|
|
15388
|
-
async function addAuthToHeaders(headers, auth) {
|
|
15389
|
-
switch (auth.type) {
|
|
15390
|
-
case "bearer": {
|
|
15391
|
-
if (auth.getToken) {
|
|
15392
|
-
const token = typeof auth.getToken === "function" ? await auth.getToken() : auth.getToken;
|
|
15393
|
-
if (token) {
|
|
15394
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
15395
|
-
}
|
|
15396
|
-
}
|
|
15397
|
-
break;
|
|
15398
|
-
}
|
|
15399
|
-
case "api-key": {
|
|
15400
|
-
if (auth.apiKey) {
|
|
15401
|
-
const headerName = auth.apiKeyHeader ?? "X-API-Key";
|
|
15402
|
-
headers[headerName] = auth.apiKey;
|
|
15403
|
-
}
|
|
15404
|
-
break;
|
|
15405
|
-
}
|
|
15406
|
-
case "basic": {
|
|
15407
|
-
if (auth.username && auth.password) {
|
|
15408
|
-
const encoded = btoa(`${auth.username}:${auth.password}`);
|
|
15409
|
-
headers["Authorization"] = `Basic ${encoded}`;
|
|
15410
|
-
}
|
|
15411
|
-
break;
|
|
15412
|
-
}
|
|
15413
|
-
case "custom": {
|
|
15414
|
-
if (auth.customAuth) {
|
|
15415
|
-
await auth.customAuth(headers);
|
|
15416
|
-
}
|
|
15417
|
-
break;
|
|
15418
|
-
}
|
|
15419
|
-
}
|
|
15420
|
-
}
|
|
15421
|
-
|
|
15422
|
-
// src/utils.ts
|
|
15423
|
-
function generateRequestId() {
|
|
15424
|
-
return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
15425
|
-
}
|
|
15426
|
-
function sleep(ms) {
|
|
15427
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15428
|
-
}
|
|
15429
|
-
function calculateRetryDelay(retry, attempt) {
|
|
15430
|
-
let delay;
|
|
15431
|
-
switch (retry.backoff) {
|
|
15432
|
-
case "fixed":
|
|
15433
|
-
delay = retry.initialDelay;
|
|
15434
|
-
break;
|
|
15435
|
-
case "linear":
|
|
15436
|
-
delay = retry.initialDelay * (attempt + 1);
|
|
15437
|
-
break;
|
|
15438
|
-
case "exponential":
|
|
15439
|
-
default:
|
|
15440
|
-
delay = retry.initialDelay * Math.pow(2, attempt);
|
|
15441
|
-
break;
|
|
15442
|
-
}
|
|
15443
|
-
const jitter = delay * 0.1 * Math.random();
|
|
15444
|
-
delay += jitter;
|
|
15445
|
-
return Math.min(delay, retry.maxDelay);
|
|
15446
|
-
}
|
|
15447
|
-
|
|
15448
|
-
// src/axios-instance.ts
|
|
15449
|
-
function shouldRetryAxios(error, retry) {
|
|
15450
|
-
if (!error.response) {
|
|
15451
|
-
return retry.retryOnNetworkError !== false;
|
|
15452
|
-
}
|
|
15453
|
-
const status = error.response.status;
|
|
15454
|
-
const retryableCodes = retry.retryableStatusCodes ?? [429, 500, 502, 503, 504];
|
|
15455
|
-
return retryableCodes.includes(status);
|
|
15456
|
-
}
|
|
17409
|
+
// src/clients/axios-client.ts
|
|
15457
17410
|
function createAxiosInstance(config) {
|
|
15458
17411
|
const instance = axios_default.create({
|
|
15459
17412
|
baseURL: config.baseUrl,
|
|
@@ -15464,247 +17417,643 @@ function createAxiosInstance(config) {
|
|
|
15464
17417
|
...config.headers
|
|
15465
17418
|
}
|
|
15466
17419
|
});
|
|
17420
|
+
const authenticator = config.auth ? createAuthenticatorFromConfig(config.auth) : noAuthenticator;
|
|
17421
|
+
const logger = config.logger;
|
|
17422
|
+
const debug = config.debug ?? false;
|
|
15467
17423
|
instance.interceptors.request.use(
|
|
15468
17424
|
async (axiosConfig) => {
|
|
17425
|
+
axiosConfig.__startTime = Date.now();
|
|
15469
17426
|
const requestId = generateRequestId();
|
|
17427
|
+
const traceId = generateTraceId();
|
|
17428
|
+
axiosConfig.__requestId = requestId;
|
|
17429
|
+
axiosConfig.__traceId = traceId;
|
|
15470
17430
|
axiosConfig.headers.set("X-Request-ID", requestId);
|
|
15471
|
-
|
|
15472
|
-
|
|
17431
|
+
axiosConfig.headers.set("X-Trace-ID", traceId);
|
|
17432
|
+
const headers = {};
|
|
17433
|
+
await authenticator.authenticate(headers);
|
|
17434
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
17435
|
+
axiosConfig.headers.set(key, value);
|
|
17436
|
+
}
|
|
17437
|
+
if (config.requestInterceptors) {
|
|
17438
|
+
await executeRequestInterceptors(config.requestInterceptors, axiosConfig);
|
|
17439
|
+
}
|
|
17440
|
+
if (debug && logger) {
|
|
17441
|
+
logger.debug(`[Request] ${axiosConfig.method?.toUpperCase()} ${axiosConfig.url}`, {
|
|
17442
|
+
requestId,
|
|
17443
|
+
headers: Object.fromEntries(
|
|
17444
|
+
Object.entries(axiosConfig.headers).filter(([, v]) => typeof v === "string")
|
|
17445
|
+
)
|
|
17446
|
+
});
|
|
15473
17447
|
}
|
|
15474
17448
|
return axiosConfig;
|
|
15475
17449
|
},
|
|
15476
17450
|
(error) => Promise.reject(error)
|
|
15477
17451
|
);
|
|
15478
17452
|
instance.interceptors.response.use(
|
|
15479
|
-
(response) =>
|
|
17453
|
+
async (response) => {
|
|
17454
|
+
const axiosConfig = response.config;
|
|
17455
|
+
const duration = Date.now() - (axiosConfig.__startTime ?? Date.now());
|
|
17456
|
+
if (debug && logger) {
|
|
17457
|
+
logger.debug(
|
|
17458
|
+
`[Response] ${axiosConfig.method?.toUpperCase()} ${axiosConfig.url} - ${response.status} (${duration}ms)`,
|
|
17459
|
+
{
|
|
17460
|
+
requestId: axiosConfig.__requestId,
|
|
17461
|
+
status: response.status,
|
|
17462
|
+
duration
|
|
17463
|
+
}
|
|
17464
|
+
);
|
|
17465
|
+
}
|
|
17466
|
+
if (config.responseInterceptors) {
|
|
17467
|
+
await executeResponseInterceptors(config.responseInterceptors, response);
|
|
17468
|
+
}
|
|
17469
|
+
return response;
|
|
17470
|
+
},
|
|
15480
17471
|
async (error) => {
|
|
15481
|
-
|
|
15482
|
-
|
|
15483
|
-
|
|
17472
|
+
const axiosConfig = error.config;
|
|
17473
|
+
const duration = Date.now() - (axiosConfig?.__startTime ?? Date.now());
|
|
17474
|
+
const customError = transformAxiosError(error);
|
|
17475
|
+
if (logger) {
|
|
17476
|
+
logger.error(
|
|
17477
|
+
`[Error] ${axiosConfig?.method?.toUpperCase()} ${axiosConfig?.url} - ${customError.message} (${duration}ms)`,
|
|
17478
|
+
{
|
|
17479
|
+
requestId: axiosConfig?.__requestId,
|
|
17480
|
+
error: customError.message,
|
|
17481
|
+
duration
|
|
17482
|
+
}
|
|
17483
|
+
);
|
|
17484
|
+
}
|
|
17485
|
+
if (config.errorInterceptors) {
|
|
17486
|
+
const recovered = await executeErrorInterceptors(
|
|
17487
|
+
config.errorInterceptors,
|
|
17488
|
+
customError,
|
|
17489
|
+
axiosConfig
|
|
17490
|
+
);
|
|
17491
|
+
if (recovered) {
|
|
17492
|
+
return recovered;
|
|
17493
|
+
}
|
|
17494
|
+
}
|
|
17495
|
+
if (config.enableRetry !== false && config.retry && shouldRetry(error, config.retry)) {
|
|
17496
|
+
const retryCount = axiosConfig?.__retryCount ?? 0;
|
|
15484
17497
|
if (retryCount < config.retry.maxRetries) {
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
17498
|
+
let delayMs = calculateRetryDelay(config.retry, retryCount);
|
|
17499
|
+
if (config.retry.respectRetryAfter !== false && error.response) {
|
|
17500
|
+
const retryAfter = parseRetryAfter(
|
|
17501
|
+
error.response.headers["retry-after"]
|
|
17502
|
+
);
|
|
17503
|
+
if (retryAfter !== void 0) {
|
|
17504
|
+
delayMs = Math.min(retryAfter * 1e3, config.retry.maxDelayMs);
|
|
17505
|
+
}
|
|
17506
|
+
}
|
|
17507
|
+
if (config.retry.onRetry) {
|
|
17508
|
+
config.retry.onRetry({
|
|
17509
|
+
attempt: retryCount + 1,
|
|
17510
|
+
maxRetries: config.retry.maxRetries,
|
|
17511
|
+
delayMs,
|
|
17512
|
+
error: customError,
|
|
17513
|
+
url: axiosConfig?.url ?? "",
|
|
17514
|
+
method: axiosConfig?.method?.toUpperCase() ?? "GET"
|
|
17515
|
+
});
|
|
17516
|
+
}
|
|
17517
|
+
if (debug && logger) {
|
|
17518
|
+
logger.debug(
|
|
17519
|
+
`[Retry] ${axiosConfig?.method?.toUpperCase()} ${axiosConfig?.url} - attempt ${retryCount + 1}/${config.retry.maxRetries}, delay ${delayMs}ms`
|
|
17520
|
+
);
|
|
17521
|
+
}
|
|
17522
|
+
await sleep(delayMs);
|
|
17523
|
+
if (axiosConfig) {
|
|
17524
|
+
axiosConfig.__retryCount = retryCount + 1;
|
|
17525
|
+
return instance.request(axiosConfig);
|
|
15490
17526
|
}
|
|
15491
17527
|
}
|
|
15492
17528
|
}
|
|
15493
|
-
return Promise.reject(
|
|
17529
|
+
return Promise.reject(customError);
|
|
15494
17530
|
}
|
|
15495
17531
|
);
|
|
15496
17532
|
return instance;
|
|
15497
17533
|
}
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
17534
|
+
async function executeRequestInterceptors(interceptors, axiosConfig) {
|
|
17535
|
+
const sorted = [...interceptors].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
17536
|
+
for (const interceptor of sorted) {
|
|
17537
|
+
const context = {
|
|
17538
|
+
requestId: axiosConfig.__requestId ?? "",
|
|
17539
|
+
traceId: axiosConfig.__traceId,
|
|
17540
|
+
startTime: axiosConfig.__startTime ?? Date.now(),
|
|
17541
|
+
retryCount: axiosConfig.__retryCount ?? 0,
|
|
17542
|
+
originalOptions: {
|
|
17543
|
+
method: axiosConfig.method?.toUpperCase() ?? "GET",
|
|
17544
|
+
path: axiosConfig.url ?? ""
|
|
17545
|
+
},
|
|
17546
|
+
options: {
|
|
17547
|
+
method: axiosConfig.method?.toUpperCase() ?? "GET",
|
|
17548
|
+
path: axiosConfig.url ?? "",
|
|
17549
|
+
headers: Object.fromEntries(
|
|
17550
|
+
Object.entries(axiosConfig.headers).filter(([, v]) => typeof v === "string")
|
|
17551
|
+
),
|
|
17552
|
+
body: axiosConfig.data,
|
|
17553
|
+
timeout: axiosConfig.timeout,
|
|
17554
|
+
metadata: {}
|
|
17555
|
+
},
|
|
17556
|
+
url: `${axiosConfig.baseURL ?? ""}${axiosConfig.url ?? ""}`,
|
|
17557
|
+
metadata: {}
|
|
15506
17558
|
};
|
|
17559
|
+
const result = await interceptor.intercept(context);
|
|
17560
|
+
if (result.headers) {
|
|
17561
|
+
for (const [key, value] of Object.entries(result.headers)) {
|
|
17562
|
+
axiosConfig.headers.set(key, value);
|
|
17563
|
+
}
|
|
17564
|
+
}
|
|
17565
|
+
if (result.timeout !== void 0) {
|
|
17566
|
+
axiosConfig.timeout = result.timeout;
|
|
17567
|
+
}
|
|
15507
17568
|
}
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
17569
|
+
}
|
|
17570
|
+
async function executeResponseInterceptors(interceptors, response) {
|
|
17571
|
+
const sorted = [...interceptors].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
17572
|
+
const axiosConfig = response.config;
|
|
17573
|
+
for (const interceptor of sorted) {
|
|
17574
|
+
const context = {
|
|
17575
|
+
requestId: axiosConfig.__requestId ?? "",
|
|
17576
|
+
traceId: axiosConfig.__traceId,
|
|
17577
|
+
startTime: axiosConfig.__startTime ?? Date.now(),
|
|
17578
|
+
retryCount: axiosConfig.__retryCount ?? 0,
|
|
17579
|
+
originalOptions: {
|
|
17580
|
+
method: axiosConfig.method?.toUpperCase() ?? "GET",
|
|
17581
|
+
path: axiosConfig.url ?? ""
|
|
17582
|
+
},
|
|
17583
|
+
options: {
|
|
17584
|
+
method: axiosConfig.method?.toUpperCase() ?? "GET",
|
|
17585
|
+
path: axiosConfig.url ?? "",
|
|
17586
|
+
headers: Object.fromEntries(
|
|
17587
|
+
Object.entries(axiosConfig.headers).filter(([, v]) => typeof v === "string")
|
|
17588
|
+
),
|
|
17589
|
+
body: axiosConfig.data,
|
|
17590
|
+
timeout: axiosConfig.timeout,
|
|
17591
|
+
metadata: {}
|
|
17592
|
+
},
|
|
17593
|
+
url: `${axiosConfig.baseURL ?? ""}${axiosConfig.url ?? ""}`,
|
|
17594
|
+
metadata: {}
|
|
17595
|
+
};
|
|
17596
|
+
const responseData = {
|
|
17597
|
+
data: response.data,
|
|
17598
|
+
status: response.status,
|
|
17599
|
+
statusText: response.statusText,
|
|
17600
|
+
headers: response.headers,
|
|
17601
|
+
requestId: axiosConfig.__requestId ?? "",
|
|
17602
|
+
traceId: axiosConfig.__traceId
|
|
17603
|
+
};
|
|
17604
|
+
await interceptor.intercept(responseData, context);
|
|
15530
17605
|
}
|
|
15531
|
-
|
|
15532
|
-
|
|
15533
|
-
|
|
15534
|
-
|
|
15535
|
-
|
|
17606
|
+
}
|
|
17607
|
+
async function executeErrorInterceptors(interceptors, error, axiosConfig) {
|
|
17608
|
+
const sorted = [...interceptors].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
17609
|
+
for (const interceptor of sorted) {
|
|
17610
|
+
const context = {
|
|
17611
|
+
requestId: axiosConfig?.__requestId ?? "",
|
|
17612
|
+
traceId: axiosConfig?.__traceId,
|
|
17613
|
+
startTime: axiosConfig?.__startTime ?? Date.now(),
|
|
17614
|
+
retryCount: axiosConfig?.__retryCount ?? 0,
|
|
17615
|
+
originalOptions: {
|
|
17616
|
+
method: axiosConfig?.method?.toUpperCase() ?? "GET",
|
|
17617
|
+
path: axiosConfig?.url ?? ""
|
|
17618
|
+
},
|
|
17619
|
+
options: {
|
|
17620
|
+
method: axiosConfig?.method?.toUpperCase() ?? "GET",
|
|
17621
|
+
path: axiosConfig?.url ?? "",
|
|
17622
|
+
headers: axiosConfig ? Object.fromEntries(
|
|
17623
|
+
Object.entries(axiosConfig.headers).filter(([, v]) => typeof v === "string")
|
|
17624
|
+
) : {},
|
|
17625
|
+
body: axiosConfig?.data,
|
|
17626
|
+
timeout: axiosConfig?.timeout,
|
|
17627
|
+
metadata: {}
|
|
17628
|
+
},
|
|
17629
|
+
url: `${axiosConfig?.baseURL ?? ""}${axiosConfig?.url ?? ""}`,
|
|
17630
|
+
metadata: {}
|
|
17631
|
+
};
|
|
17632
|
+
const result = await interceptor.intercept(error, context);
|
|
17633
|
+
if (result !== void 0) {
|
|
17634
|
+
return {
|
|
17635
|
+
data: result.data,
|
|
17636
|
+
status: result.status,
|
|
17637
|
+
statusText: result.statusText,
|
|
17638
|
+
headers: result.headers,
|
|
17639
|
+
config: axiosConfig
|
|
17640
|
+
};
|
|
17641
|
+
}
|
|
15536
17642
|
}
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
17643
|
+
return void 0;
|
|
17644
|
+
}
|
|
17645
|
+
function shouldRetry(error, retry) {
|
|
17646
|
+
if (!retry) return false;
|
|
17647
|
+
if (!error.response) {
|
|
17648
|
+
return retry.retryOnNetworkError !== false;
|
|
15542
17649
|
}
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
17650
|
+
const status = error.response.status;
|
|
17651
|
+
const retryableCodes = retry.retryableStatusCodes ?? DEFAULT_RETRY_CONFIG.retryableStatusCodes;
|
|
17652
|
+
return retryableCodes?.includes(status) ?? false;
|
|
17653
|
+
}
|
|
17654
|
+
function transformAxiosError(error) {
|
|
17655
|
+
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
17656
|
+
const timeout = error.config?.timeout ?? 3e4;
|
|
17657
|
+
return new TimeoutError(timeout);
|
|
15548
17658
|
}
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
*/
|
|
15552
|
-
async patch(path, body) {
|
|
15553
|
-
return this.request({ method: "PATCH", path, body });
|
|
17659
|
+
if (!error.response) {
|
|
17660
|
+
return new NetworkError(error.message, error);
|
|
15554
17661
|
}
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
17662
|
+
const response = error.response;
|
|
17663
|
+
const retryAfter = parseRetryAfter(
|
|
17664
|
+
response.headers["retry-after"]
|
|
17665
|
+
);
|
|
17666
|
+
return ApiError.fromResponse(
|
|
17667
|
+
{
|
|
17668
|
+
code: response.data?.code,
|
|
17669
|
+
message: response.data?.message ?? error.message,
|
|
17670
|
+
details: response.data?.details,
|
|
17671
|
+
requestId: response.data?.requestId ?? error.config?.headers?.["X-Request-ID"],
|
|
17672
|
+
traceId: response.data?.traceId
|
|
17673
|
+
},
|
|
17674
|
+
response.status,
|
|
17675
|
+
retryAfter
|
|
17676
|
+
);
|
|
17677
|
+
}
|
|
17678
|
+
|
|
17679
|
+
// src/clients/middleware-factory.ts
|
|
17680
|
+
function generateUniqueRequestKey() {
|
|
17681
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
17682
|
+
}
|
|
17683
|
+
var REQUEST_KEY_HEADER = "X-Internal-Request-Key";
|
|
17684
|
+
var retryStateMap = /* @__PURE__ */ new Map();
|
|
17685
|
+
function getRequestKey(init) {
|
|
17686
|
+
const headers = init.headers;
|
|
17687
|
+
return headers?.[REQUEST_KEY_HEADER] ?? "";
|
|
17688
|
+
}
|
|
17689
|
+
function createMiddlewares(config = {}) {
|
|
17690
|
+
const middlewares = [];
|
|
17691
|
+
middlewares.push(createRequestMiddleware(config));
|
|
17692
|
+
if (config.logger) {
|
|
17693
|
+
middlewares.push(createLoggingMiddleware(config.logger, config.debug ?? false));
|
|
15560
17694
|
}
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
*/
|
|
15564
|
-
buildUrl(path, query) {
|
|
15565
|
-
const base = this.config.baseUrl.replace(/\/$/, "");
|
|
15566
|
-
const pathname = path.startsWith("/") ? path : `/${path}`;
|
|
15567
|
-
const url2 = new URL(`${base}${pathname}`);
|
|
15568
|
-
if (query) {
|
|
15569
|
-
for (const [key, value] of Object.entries(query)) {
|
|
15570
|
-
if (value !== void 0) {
|
|
15571
|
-
url2.searchParams.set(key, String(value));
|
|
15572
|
-
}
|
|
15573
|
-
}
|
|
15574
|
-
}
|
|
15575
|
-
return url2.toString();
|
|
17695
|
+
if (config.enableRetry !== false && config.retry) {
|
|
17696
|
+
middlewares.push(createRetryMiddleware(config));
|
|
15576
17697
|
}
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
async buildHeaders(options) {
|
|
15581
|
-
const headers = {
|
|
15582
|
-
"Content-Type": "application/json",
|
|
15583
|
-
Accept: "application/json",
|
|
15584
|
-
...this.config.headers,
|
|
15585
|
-
...options.headers
|
|
15586
|
-
};
|
|
15587
|
-
if (options.context?.requestId) {
|
|
15588
|
-
headers["X-Request-ID"] = options.context.requestId;
|
|
15589
|
-
} else {
|
|
15590
|
-
headers["X-Request-ID"] = generateRequestId();
|
|
17698
|
+
if (config.preMiddlewares?.length) {
|
|
17699
|
+
for (const preFn of config.preMiddlewares) {
|
|
17700
|
+
middlewares.push({ pre: preFn });
|
|
15591
17701
|
}
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
await addAuthToHeaders(headers, this.config.auth);
|
|
17702
|
+
}
|
|
17703
|
+
if (config.postMiddlewares?.length) {
|
|
17704
|
+
for (const postFn of config.postMiddlewares) {
|
|
17705
|
+
middlewares.push({ post: postFn });
|
|
15597
17706
|
}
|
|
15598
|
-
return headers;
|
|
15599
17707
|
}
|
|
15600
|
-
|
|
15601
|
-
|
|
15602
|
-
|
|
15603
|
-
async executeWithRetry(url2, init, options) {
|
|
15604
|
-
const retry = this.config.retry || {
|
|
15605
|
-
maxRetries: 0,
|
|
15606
|
-
initialDelay: 1e3,
|
|
15607
|
-
maxDelay: 3e4,
|
|
15608
|
-
backoff: "exponential"
|
|
15609
|
-
};
|
|
15610
|
-
let lastError;
|
|
15611
|
-
let attempt = 0;
|
|
15612
|
-
while (attempt <= retry.maxRetries) {
|
|
15613
|
-
try {
|
|
15614
|
-
return await this.execute(url2, init, options);
|
|
15615
|
-
} catch (error) {
|
|
15616
|
-
lastError = error;
|
|
15617
|
-
const shouldRetry = this.shouldRetry(error, retry, attempt);
|
|
15618
|
-
if (!shouldRetry) {
|
|
15619
|
-
throw error;
|
|
15620
|
-
}
|
|
15621
|
-
const delay = calculateRetryDelay(retry, attempt);
|
|
15622
|
-
await sleep(delay);
|
|
15623
|
-
attempt++;
|
|
15624
|
-
}
|
|
17708
|
+
if (config.errorMiddlewares?.length) {
|
|
17709
|
+
for (const errorFn of config.errorMiddlewares) {
|
|
17710
|
+
middlewares.push({ onError: errorFn });
|
|
15625
17711
|
}
|
|
15626
|
-
throw lastError;
|
|
15627
17712
|
}
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15636
|
-
const
|
|
15637
|
-
|
|
15638
|
-
|
|
17713
|
+
return middlewares;
|
|
17714
|
+
}
|
|
17715
|
+
function createRequestMiddleware(config) {
|
|
17716
|
+
const authenticator = config.auth ? createAuthenticatorFromConfig(config.auth) : noAuthenticator;
|
|
17717
|
+
return {
|
|
17718
|
+
async pre(context) {
|
|
17719
|
+
const requestId = generateRequestId();
|
|
17720
|
+
const traceId = generateTraceId();
|
|
17721
|
+
const requestKey = generateUniqueRequestKey();
|
|
17722
|
+
retryStateMap.set(requestKey, {
|
|
17723
|
+
attempt: 0,
|
|
17724
|
+
startTime: Date.now(),
|
|
17725
|
+
requestId,
|
|
17726
|
+
traceId
|
|
15639
17727
|
});
|
|
15640
|
-
|
|
15641
|
-
const
|
|
15642
|
-
if (
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
details: data.details,
|
|
15650
|
-
traceId: data.traceId
|
|
15651
|
-
};
|
|
15652
|
-
const error = new ApiError(errorResponse, response.status);
|
|
15653
|
-
for (const interceptor of this.config.onError || []) {
|
|
15654
|
-
await interceptor(error, options);
|
|
17728
|
+
const existingHeaders = context.init.headers ?? {};
|
|
17729
|
+
const headers = {};
|
|
17730
|
+
if (existingHeaders instanceof Headers) {
|
|
17731
|
+
existingHeaders.forEach((value, key) => {
|
|
17732
|
+
headers[key] = value;
|
|
17733
|
+
});
|
|
17734
|
+
} else if (Array.isArray(existingHeaders)) {
|
|
17735
|
+
for (const [key, value] of existingHeaders) {
|
|
17736
|
+
headers[key] = value;
|
|
15655
17737
|
}
|
|
15656
|
-
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
17738
|
+
} else {
|
|
17739
|
+
Object.assign(headers, existingHeaders);
|
|
17740
|
+
}
|
|
17741
|
+
headers[REQUEST_KEY_HEADER] = requestKey;
|
|
17742
|
+
headers["X-Request-ID"] = requestId;
|
|
17743
|
+
headers["X-Trace-ID"] = traceId;
|
|
17744
|
+
if (config.headers) {
|
|
17745
|
+
Object.assign(headers, config.headers);
|
|
17746
|
+
}
|
|
17747
|
+
await authenticator.authenticate(headers);
|
|
17748
|
+
return {
|
|
17749
|
+
url: context.url,
|
|
17750
|
+
init: {
|
|
17751
|
+
...context.init,
|
|
17752
|
+
headers
|
|
17753
|
+
}
|
|
17754
|
+
};
|
|
17755
|
+
}
|
|
17756
|
+
};
|
|
17757
|
+
}
|
|
17758
|
+
function createLoggingMiddleware(logger, debugMode) {
|
|
17759
|
+
return {
|
|
17760
|
+
async pre(context) {
|
|
17761
|
+
if (!debugMode) return void 0;
|
|
17762
|
+
const method = context.init.method ?? "GET";
|
|
17763
|
+
const requestId = context.init.headers?.["X-Request-ID"] ?? "";
|
|
17764
|
+
logger.debug(`[Request] ${method} ${context.url}`, { requestId });
|
|
17765
|
+
return void 0;
|
|
17766
|
+
},
|
|
17767
|
+
async post(context) {
|
|
17768
|
+
const requestKey = getRequestKey(context.init);
|
|
17769
|
+
const state = retryStateMap.get(requestKey);
|
|
17770
|
+
const duration = state ? Date.now() - state.startTime : 0;
|
|
17771
|
+
if (requestKey) {
|
|
17772
|
+
retryStateMap.delete(requestKey);
|
|
17773
|
+
}
|
|
17774
|
+
if (!debugMode) return void 0;
|
|
17775
|
+
const method = context.init.method ?? "GET";
|
|
17776
|
+
const requestId = context.init.headers?.["X-Request-ID"] ?? "";
|
|
17777
|
+
logger.debug(`[Response] ${method} ${context.url} - ${context.response.status} (${duration}ms)`, {
|
|
17778
|
+
requestId,
|
|
17779
|
+
status: context.response.status,
|
|
17780
|
+
duration
|
|
17781
|
+
});
|
|
17782
|
+
return void 0;
|
|
17783
|
+
},
|
|
17784
|
+
async onError(context) {
|
|
17785
|
+
const method = context.init.method ?? "GET";
|
|
17786
|
+
const requestId = context.init.headers?.["X-Request-ID"] ?? "";
|
|
17787
|
+
const requestKey = getRequestKey(context.init);
|
|
17788
|
+
const state = retryStateMap.get(requestKey);
|
|
17789
|
+
const duration = state ? Date.now() - state.startTime : 0;
|
|
17790
|
+
const errorMessage = context.error instanceof Error ? context.error.message : String(context.error);
|
|
17791
|
+
logger.error(`[Error] ${method} ${context.url} - ${errorMessage} (${duration}ms)`, {
|
|
17792
|
+
requestId,
|
|
17793
|
+
error: errorMessage,
|
|
17794
|
+
duration
|
|
17795
|
+
});
|
|
17796
|
+
return void 0;
|
|
17797
|
+
}
|
|
17798
|
+
};
|
|
17799
|
+
}
|
|
17800
|
+
function createRetryMiddleware(config) {
|
|
17801
|
+
const retryConfig = config.retry;
|
|
17802
|
+
const maxRetries = retryConfig.maxRetries;
|
|
17803
|
+
const logger = config.logger;
|
|
17804
|
+
const debug = config.debug ?? false;
|
|
17805
|
+
return {
|
|
17806
|
+
async onError(context) {
|
|
17807
|
+
const requestKey = getRequestKey(context.init);
|
|
17808
|
+
const state = retryStateMap.get(requestKey);
|
|
17809
|
+
if (!state) {
|
|
17810
|
+
return void 0;
|
|
17811
|
+
}
|
|
17812
|
+
if (!shouldRetry2(context.error, state.attempt, maxRetries, retryConfig)) {
|
|
17813
|
+
retryStateMap.delete(requestKey);
|
|
17814
|
+
return void 0;
|
|
17815
|
+
}
|
|
17816
|
+
state.attempt++;
|
|
17817
|
+
const delay = calculateRetryDelay2(state.attempt, retryConfig);
|
|
17818
|
+
if (retryConfig.onRetry) {
|
|
17819
|
+
const method = context.init.method ?? "GET";
|
|
17820
|
+
retryConfig.onRetry({
|
|
17821
|
+
attempt: state.attempt,
|
|
17822
|
+
maxRetries,
|
|
17823
|
+
delayMs: delay,
|
|
17824
|
+
error: context.error instanceof Error ? context.error : new Error(String(context.error)),
|
|
17825
|
+
url: context.url,
|
|
17826
|
+
method
|
|
17827
|
+
});
|
|
15661
17828
|
}
|
|
15662
|
-
|
|
15663
|
-
|
|
15664
|
-
|
|
15665
|
-
if (error instanceof ApiError) {
|
|
15666
|
-
throw error;
|
|
17829
|
+
if (debug && logger) {
|
|
17830
|
+
const method = context.init.method ?? "GET";
|
|
17831
|
+
logger.debug(`[Retry] ${method} ${context.url} - attempt ${state.attempt}/${maxRetries}, delay ${delay}ms`);
|
|
15667
17832
|
}
|
|
15668
|
-
|
|
15669
|
-
|
|
17833
|
+
await sleep(delay);
|
|
17834
|
+
try {
|
|
17835
|
+
const response = await context.fetch(context.url, context.init);
|
|
17836
|
+
if (!response.ok && shouldRetryStatus(response.status, state.attempt, maxRetries, retryConfig)) {
|
|
17837
|
+
state.attempt++;
|
|
17838
|
+
const nextDelay = calculateRetryDelay2(state.attempt, retryConfig);
|
|
17839
|
+
if (debug && logger) {
|
|
17840
|
+
const method = context.init.method ?? "GET";
|
|
17841
|
+
logger.debug(`[Retry] ${method} ${context.url} - status ${response.status}, attempt ${state.attempt}/${maxRetries}, delay ${nextDelay}ms`);
|
|
17842
|
+
}
|
|
17843
|
+
await sleep(nextDelay);
|
|
17844
|
+
return context.fetch(context.url, context.init);
|
|
17845
|
+
}
|
|
17846
|
+
retryStateMap.delete(requestKey);
|
|
17847
|
+
return response;
|
|
17848
|
+
} catch (retryError) {
|
|
17849
|
+
if (state.attempt < maxRetries) {
|
|
17850
|
+
throw retryError;
|
|
17851
|
+
}
|
|
17852
|
+
retryStateMap.delete(requestKey);
|
|
17853
|
+
throw retryError;
|
|
15670
17854
|
}
|
|
15671
|
-
throw new NetworkError(error.message, error);
|
|
15672
17855
|
}
|
|
17856
|
+
};
|
|
17857
|
+
}
|
|
17858
|
+
function shouldRetry2(error, attempt, maxRetries, retryConfig) {
|
|
17859
|
+
if (attempt >= maxRetries) return false;
|
|
17860
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
17861
|
+
return false;
|
|
15673
17862
|
}
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
17863
|
+
if (error instanceof TypeError || error instanceof NetworkError) {
|
|
17864
|
+
return retryConfig.retryOnNetworkError !== false;
|
|
17865
|
+
}
|
|
17866
|
+
if (error instanceof TimeoutError) {
|
|
17867
|
+
return retryConfig.retryOnTimeout !== false;
|
|
17868
|
+
}
|
|
17869
|
+
return true;
|
|
17870
|
+
}
|
|
17871
|
+
function shouldRetryStatus(status, attempt, maxRetries, retryConfig) {
|
|
17872
|
+
if (attempt >= maxRetries) return false;
|
|
17873
|
+
const retryableCodes = retryConfig.retryableStatusCodes ?? [429, 500, 502, 503, 504];
|
|
17874
|
+
return retryableCodes.includes(status);
|
|
17875
|
+
}
|
|
17876
|
+
function calculateRetryDelay2(attempt, retryConfig) {
|
|
17877
|
+
const {
|
|
17878
|
+
initialDelayMs = 1e3,
|
|
17879
|
+
maxDelayMs = 3e4,
|
|
17880
|
+
backoffStrategy = "exponential",
|
|
17881
|
+
jitterFactor = 0.1
|
|
17882
|
+
} = retryConfig;
|
|
17883
|
+
let delay;
|
|
17884
|
+
switch (backoffStrategy) {
|
|
17885
|
+
case "fixed":
|
|
17886
|
+
delay = initialDelayMs;
|
|
17887
|
+
break;
|
|
17888
|
+
case "linear":
|
|
17889
|
+
delay = initialDelayMs * attempt;
|
|
17890
|
+
break;
|
|
17891
|
+
case "exponential":
|
|
17892
|
+
default:
|
|
17893
|
+
delay = initialDelayMs * Math.pow(2, attempt - 1);
|
|
17894
|
+
break;
|
|
17895
|
+
}
|
|
17896
|
+
delay = Math.min(delay, maxDelayMs);
|
|
17897
|
+
if (jitterFactor > 0) {
|
|
17898
|
+
const jitter = delay * jitterFactor * (Math.random() * 2 - 1);
|
|
17899
|
+
delay = Math.max(0, delay + jitter);
|
|
17900
|
+
}
|
|
17901
|
+
return Math.round(delay);
|
|
17902
|
+
}
|
|
17903
|
+
function createAuthMiddleware(authConfig) {
|
|
17904
|
+
const authenticator = createAuthenticatorFromConfig(authConfig);
|
|
17905
|
+
return {
|
|
17906
|
+
async pre(context) {
|
|
17907
|
+
const headers = {};
|
|
17908
|
+
const existingHeaders = context.init.headers ?? {};
|
|
17909
|
+
if (existingHeaders instanceof Headers) {
|
|
17910
|
+
existingHeaders.forEach((value, key) => {
|
|
17911
|
+
headers[key] = value;
|
|
17912
|
+
});
|
|
17913
|
+
} else if (Array.isArray(existingHeaders)) {
|
|
17914
|
+
for (const [key, value] of existingHeaders) {
|
|
17915
|
+
headers[key] = value;
|
|
17916
|
+
}
|
|
17917
|
+
} else {
|
|
17918
|
+
Object.assign(headers, existingHeaders);
|
|
17919
|
+
}
|
|
17920
|
+
await authenticator.authenticate(headers);
|
|
17921
|
+
return {
|
|
17922
|
+
url: context.url,
|
|
17923
|
+
init: {
|
|
17924
|
+
...context.init,
|
|
17925
|
+
headers
|
|
17926
|
+
}
|
|
17927
|
+
};
|
|
15683
17928
|
}
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
17929
|
+
};
|
|
17930
|
+
}
|
|
17931
|
+
function createTrackingMiddleware() {
|
|
17932
|
+
return {
|
|
17933
|
+
async pre(context) {
|
|
17934
|
+
const requestId = generateRequestId();
|
|
17935
|
+
const traceId = generateTraceId();
|
|
17936
|
+
const headers = {};
|
|
17937
|
+
const existingHeaders = context.init.headers ?? {};
|
|
17938
|
+
if (existingHeaders instanceof Headers) {
|
|
17939
|
+
existingHeaders.forEach((value, key) => {
|
|
17940
|
+
headers[key] = value;
|
|
17941
|
+
});
|
|
17942
|
+
} else if (Array.isArray(existingHeaders)) {
|
|
17943
|
+
for (const [key, value] of existingHeaders) {
|
|
17944
|
+
headers[key] = value;
|
|
17945
|
+
}
|
|
17946
|
+
} else {
|
|
17947
|
+
Object.assign(headers, existingHeaders);
|
|
17948
|
+
}
|
|
17949
|
+
headers["X-Request-ID"] = requestId;
|
|
17950
|
+
headers["X-Trace-ID"] = traceId;
|
|
17951
|
+
return {
|
|
17952
|
+
url: context.url,
|
|
17953
|
+
init: {
|
|
17954
|
+
...context.init,
|
|
17955
|
+
headers
|
|
17956
|
+
}
|
|
17957
|
+
};
|
|
15687
17958
|
}
|
|
15688
|
-
|
|
15689
|
-
}
|
|
15690
|
-
};
|
|
15691
|
-
function createClient(config) {
|
|
15692
|
-
return new HttpClient(config);
|
|
17959
|
+
};
|
|
15693
17960
|
}
|
|
15694
17961
|
|
|
15695
17962
|
// src/index.ts
|
|
15696
17963
|
var VERSION3 = "2.0.0";
|
|
17964
|
+
var SDK_NAME = "@djvlc/openapi-client-core";
|
|
17965
|
+
function getSdkInfo() {
|
|
17966
|
+
return { name: SDK_NAME, version: VERSION3 };
|
|
17967
|
+
}
|
|
15697
17968
|
export {
|
|
17969
|
+
AbortError,
|
|
15698
17970
|
ApiError,
|
|
15699
|
-
|
|
17971
|
+
ApiKeyAuthenticator,
|
|
17972
|
+
AuthInterceptor,
|
|
17973
|
+
BaseClient,
|
|
17974
|
+
BaseClientError,
|
|
17975
|
+
BasicAuthenticator,
|
|
17976
|
+
BearerAuthenticator,
|
|
17977
|
+
BufferLogger,
|
|
17978
|
+
ConsoleLogger,
|
|
17979
|
+
CustomAuthenticator,
|
|
17980
|
+
DEFAULT_HEADERS,
|
|
17981
|
+
DEFAULT_RETRY_CONFIG,
|
|
17982
|
+
DefaultMetricsCollector,
|
|
17983
|
+
ErrorTransformInterceptor,
|
|
17984
|
+
FetchClient,
|
|
17985
|
+
InterceptorManager,
|
|
17986
|
+
LOG_LEVEL_PRIORITY,
|
|
17987
|
+
LoggingInterceptor,
|
|
17988
|
+
MetricsErrorInterceptor,
|
|
17989
|
+
MetricsRequestInterceptor,
|
|
17990
|
+
MetricsResponseInterceptor,
|
|
15700
17991
|
NetworkError,
|
|
17992
|
+
NoAuthenticator,
|
|
17993
|
+
ReportingInterceptor,
|
|
17994
|
+
RequestDeduper,
|
|
17995
|
+
RequestIdInterceptor,
|
|
17996
|
+
RetryInterceptor,
|
|
17997
|
+
SDK_NAME,
|
|
17998
|
+
SilentLogger,
|
|
15701
17999
|
TimeoutError,
|
|
18000
|
+
TimeoutInterceptor,
|
|
18001
|
+
TokenRefreshInterceptor,
|
|
18002
|
+
TraceInterceptor,
|
|
15702
18003
|
VERSION3 as VERSION,
|
|
18004
|
+
buildUrl,
|
|
15703
18005
|
calculateRetryDelay,
|
|
18006
|
+
createApiKeyAuthenticator,
|
|
18007
|
+
createAuthInterceptor,
|
|
18008
|
+
createAuthMiddleware,
|
|
18009
|
+
createAuthenticatorFromConfig,
|
|
15704
18010
|
createAxiosInstance,
|
|
15705
|
-
|
|
18011
|
+
createBasicAuthenticator,
|
|
18012
|
+
createBearerAuthenticator,
|
|
18013
|
+
createBufferLogger,
|
|
18014
|
+
createConsoleLogger,
|
|
18015
|
+
createCustomAuthenticator,
|
|
18016
|
+
createErrorTransformInterceptor,
|
|
18017
|
+
createFetchClient,
|
|
18018
|
+
createInterceptorManager,
|
|
18019
|
+
createLoggingInterceptor,
|
|
18020
|
+
createMetricsCollector,
|
|
18021
|
+
createMetricsInterceptors,
|
|
18022
|
+
createMiddlewares,
|
|
18023
|
+
createNoAuthenticator,
|
|
18024
|
+
createReportingInterceptor,
|
|
18025
|
+
createRequestDeduper,
|
|
18026
|
+
createRequestIdInterceptor,
|
|
18027
|
+
createRetryInterceptor,
|
|
18028
|
+
createSilentLogger,
|
|
18029
|
+
createTimeoutInterceptor,
|
|
18030
|
+
createTokenRefreshInterceptor,
|
|
18031
|
+
createTraceInterceptor,
|
|
18032
|
+
createTrackingMiddleware,
|
|
18033
|
+
extractPath,
|
|
18034
|
+
extractResponseHeaders,
|
|
15706
18035
|
generateRequestId,
|
|
15707
|
-
|
|
18036
|
+
generateTraceId,
|
|
18037
|
+
getErrorType,
|
|
18038
|
+
getHeader,
|
|
18039
|
+
getRecommendedRetryDelay,
|
|
18040
|
+
getRetryDelay,
|
|
18041
|
+
getSdkInfo,
|
|
18042
|
+
hasContentType,
|
|
18043
|
+
isClientError,
|
|
18044
|
+
isRetryableError,
|
|
18045
|
+
isValidRequestId,
|
|
18046
|
+
isValidTraceId,
|
|
18047
|
+
joinPaths,
|
|
18048
|
+
mergeHeaders,
|
|
18049
|
+
noAuthenticator,
|
|
18050
|
+
parseQueryString,
|
|
18051
|
+
parseRetryAfter,
|
|
18052
|
+
removeHeader,
|
|
18053
|
+
setHeader,
|
|
18054
|
+
silentLogger,
|
|
18055
|
+
sleep,
|
|
18056
|
+
sleepWithAbort
|
|
15708
18057
|
};
|
|
15709
18058
|
/*! Bundled license information:
|
|
15710
18059
|
|
|
@@ -15724,4 +18073,3 @@ mime-types/index.js:
|
|
|
15724
18073
|
* MIT Licensed
|
|
15725
18074
|
*)
|
|
15726
18075
|
*/
|
|
15727
|
-
//# sourceMappingURL=index.mjs.map
|