@aigne/afs-http 1.11.0-beta.3
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/LICENSE.md +26 -0
- package/README.md +318 -0
- package/dist/adapters/express.cjs +74 -0
- package/dist/adapters/express.d.cts +56 -0
- package/dist/adapters/express.d.cts.map +1 -0
- package/dist/adapters/express.d.mts +56 -0
- package/dist/adapters/express.d.mts.map +1 -0
- package/dist/adapters/express.mjs +74 -0
- package/dist/adapters/express.mjs.map +1 -0
- package/dist/adapters/koa.cjs +73 -0
- package/dist/adapters/koa.d.cts +56 -0
- package/dist/adapters/koa.d.cts.map +1 -0
- package/dist/adapters/koa.d.mts +56 -0
- package/dist/adapters/koa.d.mts.map +1 -0
- package/dist/adapters/koa.mjs +73 -0
- package/dist/adapters/koa.mjs.map +1 -0
- package/dist/client.cjs +143 -0
- package/dist/client.d.cts +70 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +70 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +144 -0
- package/dist/client.mjs.map +1 -0
- package/dist/errors.cjs +105 -0
- package/dist/errors.d.cts +63 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +63 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +98 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/handler.cjs +126 -0
- package/dist/handler.d.cts +43 -0
- package/dist/handler.d.cts.map +1 -0
- package/dist/handler.d.mts +43 -0
- package/dist/handler.d.mts.map +1 -0
- package/dist/handler.mjs +127 -0
- package/dist/handler.mjs.map +1 -0
- package/dist/index.cjs +33 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +9 -0
- package/dist/protocol.cjs +68 -0
- package/dist/protocol.d.cts +119 -0
- package/dist/protocol.d.cts.map +1 -0
- package/dist/protocol.d.mts +119 -0
- package/dist/protocol.d.mts.map +1 -0
- package/dist/protocol.mjs +64 -0
- package/dist/protocol.mjs.map +1 -0
- package/dist/retry.cjs +111 -0
- package/dist/retry.d.cts +57 -0
- package/dist/retry.d.cts.map +1 -0
- package/dist/retry.d.mts +57 -0
- package/dist/retry.d.mts.map +1 -0
- package/dist/retry.mjs +105 -0
- package/dist/retry.mjs.map +1 -0
- package/package.json +55 -0
package/dist/retry.cjs
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/retry.ts
|
|
3
|
+
/**
|
|
4
|
+
* Default retry options
|
|
5
|
+
*/
|
|
6
|
+
const DEFAULT_RETRY_OPTIONS = {
|
|
7
|
+
maxRetries: 3,
|
|
8
|
+
initialDelay: 1e3,
|
|
9
|
+
maxDelay: 3e4,
|
|
10
|
+
multiplier: 2
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Calculate delay for a given retry attempt using exponential backoff
|
|
14
|
+
* @param attempt - The retry attempt number (0-indexed)
|
|
15
|
+
* @param options - Retry options
|
|
16
|
+
* @returns Delay in milliseconds
|
|
17
|
+
*/
|
|
18
|
+
function calculateDelay(attempt, options = {}) {
|
|
19
|
+
const initialDelay = options.initialDelay ?? DEFAULT_RETRY_OPTIONS.initialDelay;
|
|
20
|
+
const maxDelay = options.maxDelay ?? DEFAULT_RETRY_OPTIONS.maxDelay;
|
|
21
|
+
const delay = initialDelay * (options.multiplier ?? DEFAULT_RETRY_OPTIONS.multiplier) ** attempt;
|
|
22
|
+
return Math.min(delay, maxDelay);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sleep for a given number of milliseconds
|
|
26
|
+
*/
|
|
27
|
+
function sleep(ms) {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Default function to check if a fetch error is retryable
|
|
32
|
+
*/
|
|
33
|
+
function isRetryableError(error) {
|
|
34
|
+
const message = error.message.toLowerCase();
|
|
35
|
+
return [
|
|
36
|
+
"econnreset",
|
|
37
|
+
"etimedout",
|
|
38
|
+
"enotfound",
|
|
39
|
+
"econnrefused",
|
|
40
|
+
"epipe",
|
|
41
|
+
"network",
|
|
42
|
+
"fetch failed",
|
|
43
|
+
"socket hang up",
|
|
44
|
+
"connection reset",
|
|
45
|
+
"timeout"
|
|
46
|
+
].some((pattern) => message.includes(pattern));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if an HTTP response status is retryable
|
|
50
|
+
*/
|
|
51
|
+
function isRetryableStatus(status) {
|
|
52
|
+
if (status >= 500 && status < 600) return true;
|
|
53
|
+
if (status === 429) return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute a function with retry logic
|
|
58
|
+
* @param fn - The async function to execute
|
|
59
|
+
* @param options - Retry options
|
|
60
|
+
* @returns The result of the function
|
|
61
|
+
*/
|
|
62
|
+
async function withRetry(fn, options = {}) {
|
|
63
|
+
const maxRetries = options.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;
|
|
64
|
+
const isRetryable = options.isRetryable ?? ((error) => isRetryableError(error));
|
|
65
|
+
let lastError;
|
|
66
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
67
|
+
return await fn();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
70
|
+
if (!(attempt < maxRetries && isRetryable(lastError))) throw lastError;
|
|
71
|
+
await sleep(calculateDelay(attempt, options));
|
|
72
|
+
}
|
|
73
|
+
throw lastError ?? /* @__PURE__ */ new Error("Retry failed");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Execute a fetch request with retry logic
|
|
77
|
+
* @param url - The URL to fetch
|
|
78
|
+
* @param init - Fetch init options
|
|
79
|
+
* @param retryOptions - Retry options
|
|
80
|
+
* @returns The fetch response
|
|
81
|
+
*/
|
|
82
|
+
async function fetchWithRetry(url, init, retryOptions = {}) {
|
|
83
|
+
const maxRetries = retryOptions.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;
|
|
84
|
+
let lastError;
|
|
85
|
+
let lastResponse;
|
|
86
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
87
|
+
const response = await fetch(url, init);
|
|
88
|
+
lastResponse = response;
|
|
89
|
+
if (isRetryableStatus(response.status) && attempt < maxRetries) {
|
|
90
|
+
await sleep(calculateDelay(attempt, retryOptions));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
return response;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
96
|
+
const isRetryable = retryOptions.isRetryable ?? isRetryableError;
|
|
97
|
+
if (!(attempt < maxRetries && isRetryable(lastError))) throw lastError;
|
|
98
|
+
await sleep(calculateDelay(attempt, retryOptions));
|
|
99
|
+
}
|
|
100
|
+
if (lastResponse) return lastResponse;
|
|
101
|
+
throw lastError ?? /* @__PURE__ */ new Error("Fetch failed after retries");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
exports.DEFAULT_RETRY_OPTIONS = DEFAULT_RETRY_OPTIONS;
|
|
106
|
+
exports.calculateDelay = calculateDelay;
|
|
107
|
+
exports.fetchWithRetry = fetchWithRetry;
|
|
108
|
+
exports.isRetryableError = isRetryableError;
|
|
109
|
+
exports.isRetryableStatus = isRetryableStatus;
|
|
110
|
+
exports.sleep = sleep;
|
|
111
|
+
exports.withRetry = withRetry;
|
package/dist/retry.d.cts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/retry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Retry configuration options
|
|
4
|
+
*/
|
|
5
|
+
interface RetryOptions {
|
|
6
|
+
/** Maximum number of retries (default: 3) */
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
/** Initial delay in milliseconds (default: 1000) */
|
|
9
|
+
initialDelay?: number;
|
|
10
|
+
/** Maximum delay in milliseconds (default: 30000) */
|
|
11
|
+
maxDelay?: number;
|
|
12
|
+
/** Delay multiplier for exponential backoff (default: 2) */
|
|
13
|
+
multiplier?: number;
|
|
14
|
+
/** Function to determine if an error is retryable */
|
|
15
|
+
isRetryable?: (error: Error, response?: Response) => boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Default retry options
|
|
19
|
+
*/
|
|
20
|
+
declare const DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, "isRetryable">>;
|
|
21
|
+
/**
|
|
22
|
+
* Calculate delay for a given retry attempt using exponential backoff
|
|
23
|
+
* @param attempt - The retry attempt number (0-indexed)
|
|
24
|
+
* @param options - Retry options
|
|
25
|
+
* @returns Delay in milliseconds
|
|
26
|
+
*/
|
|
27
|
+
declare function calculateDelay(attempt: number, options?: Pick<RetryOptions, "initialDelay" | "maxDelay" | "multiplier">): number;
|
|
28
|
+
/**
|
|
29
|
+
* Sleep for a given number of milliseconds
|
|
30
|
+
*/
|
|
31
|
+
declare function sleep(ms: number): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Default function to check if a fetch error is retryable
|
|
34
|
+
*/
|
|
35
|
+
declare function isRetryableError(error: Error): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Check if an HTTP response status is retryable
|
|
38
|
+
*/
|
|
39
|
+
declare function isRetryableStatus(status: number): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Execute a function with retry logic
|
|
42
|
+
* @param fn - The async function to execute
|
|
43
|
+
* @param options - Retry options
|
|
44
|
+
* @returns The result of the function
|
|
45
|
+
*/
|
|
46
|
+
declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a fetch request with retry logic
|
|
49
|
+
* @param url - The URL to fetch
|
|
50
|
+
* @param init - Fetch init options
|
|
51
|
+
* @param retryOptions - Retry options
|
|
52
|
+
* @returns The fetch response
|
|
53
|
+
*/
|
|
54
|
+
declare function fetchWithRetry(url: string, init?: RequestInit, retryOptions?: RetryOptions): Promise<Response>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { DEFAULT_RETRY_OPTIONS, RetryOptions, calculateDelay, fetchWithRetry, isRetryableError, isRetryableStatus, sleep, withRetry };
|
|
57
|
+
//# sourceMappingURL=retry.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.cts","names":[],"sources":["../src/retry.ts"],"mappings":";;AAGA;AAgBA;UAhBiB,YAAA;EAAA;EAAA,UAAA;EAAA;EAAA,YAAA;EAAA;EAAA,QAAA;EAAA;EAAA,UAAA;EAAA;EAAA,WAAA,IAAA,KAAA,EAUO,KAAA,EAAA,QAAA,GAAkB,QAAA;AAAA;AAAA;AAM1C;;AAN0C,cAM7B,qBAAA,EAAuB,QAAA,CAAS,IAAA,CAAK,YAAA;AAAA;;;;AAalD;AAeA;AA5BkD,iBAalC,cAAA,CAAA,OAAA,UAAA,OAAA,GAEL,IAAA,CAAK,YAAA;AAAA;AAahB;AAOA;AApBgB,iBAaA,KAAA,CAAA,EAAA,WAAmB,OAAA;AAAA;AAOnC;AAuBA;AA9BmC,iBAOnB,gBAAA,CAAA,KAAA,EAAwB,KAAA;AAAA;AAuBxC;AAkBA;AAzCwC,iBAuBxB,iBAAA,CAAA,MAAA;AAAA;AAkBhB;;;;;AAlBgB,iBAkBM,SAAA,GAAA,CAAA,EAAA,QAAuB,OAAA,CAAQ,CAAA,GAAA,OAAA,GAAa,YAAA,GAAoB,OAAA,CAAQ,CAAA;AAAA;;;AAoC9F;;;;AApC8F,iBAoCxE,cAAA,CAAA,GAAA,UAAA,IAAA,GAEb,WAAA,EAAA,YAAA,GACO,YAAA,GACb,OAAA,CAAQ,QAAA"}
|
package/dist/retry.d.mts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/retry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Retry configuration options
|
|
4
|
+
*/
|
|
5
|
+
interface RetryOptions {
|
|
6
|
+
/** Maximum number of retries (default: 3) */
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
/** Initial delay in milliseconds (default: 1000) */
|
|
9
|
+
initialDelay?: number;
|
|
10
|
+
/** Maximum delay in milliseconds (default: 30000) */
|
|
11
|
+
maxDelay?: number;
|
|
12
|
+
/** Delay multiplier for exponential backoff (default: 2) */
|
|
13
|
+
multiplier?: number;
|
|
14
|
+
/** Function to determine if an error is retryable */
|
|
15
|
+
isRetryable?: (error: Error, response?: Response) => boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Default retry options
|
|
19
|
+
*/
|
|
20
|
+
declare const DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, "isRetryable">>;
|
|
21
|
+
/**
|
|
22
|
+
* Calculate delay for a given retry attempt using exponential backoff
|
|
23
|
+
* @param attempt - The retry attempt number (0-indexed)
|
|
24
|
+
* @param options - Retry options
|
|
25
|
+
* @returns Delay in milliseconds
|
|
26
|
+
*/
|
|
27
|
+
declare function calculateDelay(attempt: number, options?: Pick<RetryOptions, "initialDelay" | "maxDelay" | "multiplier">): number;
|
|
28
|
+
/**
|
|
29
|
+
* Sleep for a given number of milliseconds
|
|
30
|
+
*/
|
|
31
|
+
declare function sleep(ms: number): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Default function to check if a fetch error is retryable
|
|
34
|
+
*/
|
|
35
|
+
declare function isRetryableError(error: Error): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Check if an HTTP response status is retryable
|
|
38
|
+
*/
|
|
39
|
+
declare function isRetryableStatus(status: number): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Execute a function with retry logic
|
|
42
|
+
* @param fn - The async function to execute
|
|
43
|
+
* @param options - Retry options
|
|
44
|
+
* @returns The result of the function
|
|
45
|
+
*/
|
|
46
|
+
declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a fetch request with retry logic
|
|
49
|
+
* @param url - The URL to fetch
|
|
50
|
+
* @param init - Fetch init options
|
|
51
|
+
* @param retryOptions - Retry options
|
|
52
|
+
* @returns The fetch response
|
|
53
|
+
*/
|
|
54
|
+
declare function fetchWithRetry(url: string, init?: RequestInit, retryOptions?: RetryOptions): Promise<Response>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { DEFAULT_RETRY_OPTIONS, RetryOptions, calculateDelay, fetchWithRetry, isRetryableError, isRetryableStatus, sleep, withRetry };
|
|
57
|
+
//# sourceMappingURL=retry.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.mts","names":[],"sources":["../src/retry.ts"],"mappings":";;AAGA;AAgBA;UAhBiB,YAAA;EAAA;EAAA,UAAA;EAAA;EAAA,YAAA;EAAA;EAAA,QAAA;EAAA;EAAA,UAAA;EAAA;EAAA,WAAA,IAAA,KAAA,EAUO,KAAA,EAAA,QAAA,GAAkB,QAAA;AAAA;AAAA;AAM1C;;AAN0C,cAM7B,qBAAA,EAAuB,QAAA,CAAS,IAAA,CAAK,YAAA;AAAA;;;;AAalD;AAeA;AA5BkD,iBAalC,cAAA,CAAA,OAAA,UAAA,OAAA,GAEL,IAAA,CAAK,YAAA;AAAA;AAahB;AAOA;AApBgB,iBAaA,KAAA,CAAA,EAAA,WAAmB,OAAA;AAAA;AAOnC;AAuBA;AA9BmC,iBAOnB,gBAAA,CAAA,KAAA,EAAwB,KAAA;AAAA;AAuBxC;AAkBA;AAzCwC,iBAuBxB,iBAAA,CAAA,MAAA;AAAA;AAkBhB;;;;;AAlBgB,iBAkBM,SAAA,GAAA,CAAA,EAAA,QAAuB,OAAA,CAAQ,CAAA,GAAA,OAAA,GAAa,YAAA,GAAoB,OAAA,CAAQ,CAAA;AAAA;;;AAoC9F;;;;AApC8F,iBAoCxE,cAAA,CAAA,GAAA,UAAA,IAAA,GAEb,WAAA,EAAA,YAAA,GACO,YAAA,GACb,OAAA,CAAQ,QAAA"}
|
package/dist/retry.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//#region src/retry.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default retry options
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_RETRY_OPTIONS = {
|
|
6
|
+
maxRetries: 3,
|
|
7
|
+
initialDelay: 1e3,
|
|
8
|
+
maxDelay: 3e4,
|
|
9
|
+
multiplier: 2
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Calculate delay for a given retry attempt using exponential backoff
|
|
13
|
+
* @param attempt - The retry attempt number (0-indexed)
|
|
14
|
+
* @param options - Retry options
|
|
15
|
+
* @returns Delay in milliseconds
|
|
16
|
+
*/
|
|
17
|
+
function calculateDelay(attempt, options = {}) {
|
|
18
|
+
const initialDelay = options.initialDelay ?? DEFAULT_RETRY_OPTIONS.initialDelay;
|
|
19
|
+
const maxDelay = options.maxDelay ?? DEFAULT_RETRY_OPTIONS.maxDelay;
|
|
20
|
+
const delay = initialDelay * (options.multiplier ?? DEFAULT_RETRY_OPTIONS.multiplier) ** attempt;
|
|
21
|
+
return Math.min(delay, maxDelay);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Sleep for a given number of milliseconds
|
|
25
|
+
*/
|
|
26
|
+
function sleep(ms) {
|
|
27
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Default function to check if a fetch error is retryable
|
|
31
|
+
*/
|
|
32
|
+
function isRetryableError(error) {
|
|
33
|
+
const message = error.message.toLowerCase();
|
|
34
|
+
return [
|
|
35
|
+
"econnreset",
|
|
36
|
+
"etimedout",
|
|
37
|
+
"enotfound",
|
|
38
|
+
"econnrefused",
|
|
39
|
+
"epipe",
|
|
40
|
+
"network",
|
|
41
|
+
"fetch failed",
|
|
42
|
+
"socket hang up",
|
|
43
|
+
"connection reset",
|
|
44
|
+
"timeout"
|
|
45
|
+
].some((pattern) => message.includes(pattern));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if an HTTP response status is retryable
|
|
49
|
+
*/
|
|
50
|
+
function isRetryableStatus(status) {
|
|
51
|
+
if (status >= 500 && status < 600) return true;
|
|
52
|
+
if (status === 429) return true;
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Execute a function with retry logic
|
|
57
|
+
* @param fn - The async function to execute
|
|
58
|
+
* @param options - Retry options
|
|
59
|
+
* @returns The result of the function
|
|
60
|
+
*/
|
|
61
|
+
async function withRetry(fn, options = {}) {
|
|
62
|
+
const maxRetries = options.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;
|
|
63
|
+
const isRetryable = options.isRetryable ?? ((error) => isRetryableError(error));
|
|
64
|
+
let lastError;
|
|
65
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
66
|
+
return await fn();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
69
|
+
if (!(attempt < maxRetries && isRetryable(lastError))) throw lastError;
|
|
70
|
+
await sleep(calculateDelay(attempt, options));
|
|
71
|
+
}
|
|
72
|
+
throw lastError ?? /* @__PURE__ */ new Error("Retry failed");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Execute a fetch request with retry logic
|
|
76
|
+
* @param url - The URL to fetch
|
|
77
|
+
* @param init - Fetch init options
|
|
78
|
+
* @param retryOptions - Retry options
|
|
79
|
+
* @returns The fetch response
|
|
80
|
+
*/
|
|
81
|
+
async function fetchWithRetry(url, init, retryOptions = {}) {
|
|
82
|
+
const maxRetries = retryOptions.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;
|
|
83
|
+
let lastError;
|
|
84
|
+
let lastResponse;
|
|
85
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
86
|
+
const response = await fetch(url, init);
|
|
87
|
+
lastResponse = response;
|
|
88
|
+
if (isRetryableStatus(response.status) && attempt < maxRetries) {
|
|
89
|
+
await sleep(calculateDelay(attempt, retryOptions));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
return response;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
95
|
+
const isRetryable = retryOptions.isRetryable ?? isRetryableError;
|
|
96
|
+
if (!(attempt < maxRetries && isRetryable(lastError))) throw lastError;
|
|
97
|
+
await sleep(calculateDelay(attempt, retryOptions));
|
|
98
|
+
}
|
|
99
|
+
if (lastResponse) return lastResponse;
|
|
100
|
+
throw lastError ?? /* @__PURE__ */ new Error("Fetch failed after retries");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { DEFAULT_RETRY_OPTIONS, calculateDelay, fetchWithRetry, isRetryableError, isRetryableStatus, sleep, withRetry };
|
|
105
|
+
//# sourceMappingURL=retry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.mjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["/**\n * Retry configuration options\n */\nexport interface RetryOptions {\n /** Maximum number of retries (default: 3) */\n maxRetries?: number;\n /** Initial delay in milliseconds (default: 1000) */\n initialDelay?: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay?: number;\n /** Delay multiplier for exponential backoff (default: 2) */\n multiplier?: number;\n /** Function to determine if an error is retryable */\n isRetryable?: (error: Error, response?: Response) => boolean;\n}\n\n/**\n * Default retry options\n */\nexport const DEFAULT_RETRY_OPTIONS: Required<Omit<RetryOptions, \"isRetryable\">> = {\n maxRetries: 3,\n initialDelay: 1000,\n maxDelay: 30000,\n multiplier: 2,\n};\n\n/**\n * Calculate delay for a given retry attempt using exponential backoff\n * @param attempt - The retry attempt number (0-indexed)\n * @param options - Retry options\n * @returns Delay in milliseconds\n */\nexport function calculateDelay(\n attempt: number,\n options: Pick<RetryOptions, \"initialDelay\" | \"maxDelay\" | \"multiplier\"> = {},\n): number {\n const initialDelay = options.initialDelay ?? DEFAULT_RETRY_OPTIONS.initialDelay;\n const maxDelay = options.maxDelay ?? DEFAULT_RETRY_OPTIONS.maxDelay;\n const multiplier = options.multiplier ?? DEFAULT_RETRY_OPTIONS.multiplier;\n\n const delay = initialDelay * multiplier ** attempt;\n return Math.min(delay, maxDelay);\n}\n\n/**\n * Sleep for a given number of milliseconds\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Default function to check if a fetch error is retryable\n */\nexport function isRetryableError(error: Error): boolean {\n const message = error.message.toLowerCase();\n\n // Network errors that are typically transient\n const retryablePatterns = [\n \"econnreset\",\n \"etimedout\",\n \"enotfound\",\n \"econnrefused\",\n \"epipe\",\n \"network\",\n \"fetch failed\",\n \"socket hang up\",\n \"connection reset\",\n \"timeout\",\n ];\n\n return retryablePatterns.some((pattern) => message.includes(pattern));\n}\n\n/**\n * Check if an HTTP response status is retryable\n */\nexport function isRetryableStatus(status: number): boolean {\n // 5xx server errors are retryable\n if (status >= 500 && status < 600) {\n return true;\n }\n // 429 Too Many Requests is retryable\n if (status === 429) {\n return true;\n }\n return false;\n}\n\n/**\n * Execute a function with retry logic\n * @param fn - The async function to execute\n * @param options - Retry options\n * @returns The result of the function\n */\nexport async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {\n const maxRetries = options.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;\n const isRetryable = options.isRetryable ?? ((error: Error) => isRetryableError(error));\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if we should retry\n const shouldRetry = attempt < maxRetries && isRetryable(lastError);\n\n if (!shouldRetry) {\n throw lastError;\n }\n\n // Calculate and wait for the delay\n const delay = calculateDelay(attempt, options);\n await sleep(delay);\n }\n }\n\n // This should never be reached, but TypeScript needs it\n throw lastError ?? new Error(\"Retry failed\");\n}\n\n/**\n * Execute a fetch request with retry logic\n * @param url - The URL to fetch\n * @param init - Fetch init options\n * @param retryOptions - Retry options\n * @returns The fetch response\n */\nexport async function fetchWithRetry(\n url: string,\n init?: RequestInit,\n retryOptions: RetryOptions = {},\n): Promise<Response> {\n const maxRetries = retryOptions.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;\n\n let lastError: Error | undefined;\n let lastResponse: Response | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, init);\n lastResponse = response;\n\n // Check if the response status is retryable\n if (isRetryableStatus(response.status) && attempt < maxRetries) {\n const delay = calculateDelay(attempt, retryOptions);\n await sleep(delay);\n continue;\n }\n\n return response;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if we should retry\n const isRetryable = retryOptions.isRetryable ?? isRetryableError;\n const shouldRetry = attempt < maxRetries && isRetryable(lastError);\n\n if (!shouldRetry) {\n throw lastError;\n }\n\n // Calculate and wait for the delay\n const delay = calculateDelay(attempt, retryOptions);\n await sleep(delay);\n }\n }\n\n // If we have a response (from retryable status), return it\n if (lastResponse) {\n return lastResponse;\n }\n\n // Otherwise throw the last error\n throw lastError ?? new Error(\"Fetch failed after retries\");\n}\n"],"mappings":";;;;AAmBA,MAAa,wBAAqE;CAChF,YAAY;CACZ,cAAc;CACd,UAAU;CACV,YAAY;CACb;;;;;;;AAQD,SAAgB,eACd,SACA,UAA0E,EAAE,EACpE;CACR,MAAM,eAAe,QAAQ,gBAAgB,sBAAsB;CACnE,MAAM,WAAW,QAAQ,YAAY,sBAAsB;CAG3D,MAAM,QAAQ,gBAFK,QAAQ,cAAc,sBAAsB,eAEpB;AAC3C,QAAO,KAAK,IAAI,OAAO,SAAS;;;;;AAMlC,SAAgB,MAAM,IAA2B;AAC/C,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;AAM1D,SAAgB,iBAAiB,OAAuB;CACtD,MAAM,UAAU,MAAM,QAAQ,aAAa;AAgB3C,QAb0B;EACxB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAEwB,MAAM,YAAY,QAAQ,SAAS,QAAQ,CAAC;;;;;AAMvE,SAAgB,kBAAkB,QAAyB;AAEzD,KAAI,UAAU,OAAO,SAAS,IAC5B,QAAO;AAGT,KAAI,WAAW,IACb,QAAO;AAET,QAAO;;;;;;;;AAST,eAAsB,UAAa,IAAsB,UAAwB,EAAE,EAAc;CAC/F,MAAM,aAAa,QAAQ,cAAc,sBAAsB;CAC/D,MAAM,cAAc,QAAQ,iBAAiB,UAAiB,iBAAiB,MAAM;CAErF,IAAI;AAEJ,MAAK,IAAI,UAAU,GAAG,WAAW,YAAY,UAC3C,KAAI;AACF,SAAO,MAAM,IAAI;UACV,OAAO;AACd,cAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAKrE,MAAI,EAFgB,UAAU,cAAc,YAAY,UAAU,EAGhE,OAAM;AAKR,QAAM,MADQ,eAAe,SAAS,QAAQ,CAC5B;;AAKtB,OAAM,6BAAa,IAAI,MAAM,eAAe;;;;;;;;;AAU9C,eAAsB,eACpB,KACA,MACA,eAA6B,EAAE,EACZ;CACnB,MAAM,aAAa,aAAa,cAAc,sBAAsB;CAEpE,IAAI;CACJ,IAAI;AAEJ,MAAK,IAAI,UAAU,GAAG,WAAW,YAAY,UAC3C,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AACvC,iBAAe;AAGf,MAAI,kBAAkB,SAAS,OAAO,IAAI,UAAU,YAAY;AAE9D,SAAM,MADQ,eAAe,SAAS,aAAa,CACjC;AAClB;;AAGF,SAAO;UACA,OAAO;AACd,cAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;EAGrE,MAAM,cAAc,aAAa,eAAe;AAGhD,MAAI,EAFgB,UAAU,cAAc,YAAY,UAAU,EAGhE,OAAM;AAKR,QAAM,MADQ,eAAe,SAAS,aAAa,CACjC;;AAKtB,KAAI,aACF,QAAO;AAIT,OAAM,6BAAa,IAAI,MAAM,6BAA6B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aigne/afs-http",
|
|
3
|
+
"version": "1.11.0-beta.3",
|
|
4
|
+
"description": "AIGNE AFS HTTP transport provider for remote AFS access",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"author": "Arcblock <blocklet@arcblock.io> https://github.com/arcblock",
|
|
10
|
+
"homepage": "https://github.com/arcblock/afs",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/arcblock/afs"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/arcblock/afs/issues"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"require": "./dist/index.cjs",
|
|
25
|
+
"import": "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./*": "./*"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md",
|
|
33
|
+
"CHANGELOG.md"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"zod": "^3.25.67",
|
|
37
|
+
"@aigne/afs": "^1.11.0-beta.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/bun": "^1.3.6",
|
|
41
|
+
"npm-run-all": "^4.1.5",
|
|
42
|
+
"rimraf": "^6.1.2",
|
|
43
|
+
"tsdown": "0.20.0-beta.3",
|
|
44
|
+
"typescript": "5.9.2",
|
|
45
|
+
"@aigne/scripts": "0.0.0",
|
|
46
|
+
"@aigne/typescript-config": "0.0.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown",
|
|
50
|
+
"check-types": "tsc --noEmit",
|
|
51
|
+
"clean": "rimraf dist coverage",
|
|
52
|
+
"test": "bun test",
|
|
53
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text"
|
|
54
|
+
}
|
|
55
|
+
}
|