@sebspark/retry 1.0.16 → 1.0.18
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 +13 -0
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,8 +11,21 @@ import axios from 'axios'
|
|
|
11
11
|
const settings: RetrySettings = {
|
|
12
12
|
interval: interval.exponential(1000), // Exponential backoff starting at 1000 ms
|
|
13
13
|
maxRetries: 5,
|
|
14
|
+
maxDelay: 5000, // Cap each retry wait to at most 5000 ms
|
|
14
15
|
retryCondition: retryCondition.serverErrors, // Only retries on server errors
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const result = await retry(() => axios.get('https://example.com'), settings)
|
|
18
19
|
```
|
|
20
|
+
|
|
21
|
+
## `maxDelay`
|
|
22
|
+
|
|
23
|
+
`maxDelay` is optional. When set, it caps the computed delay for each retry attempt.
|
|
24
|
+
|
|
25
|
+
- Without `maxDelay`: waits exactly `interval(retryCount)`
|
|
26
|
+
- With `maxDelay`: waits `Math.min(interval(retryCount), maxDelay)`
|
|
27
|
+
|
|
28
|
+
Example with exponential backoff:
|
|
29
|
+
|
|
30
|
+
- `interval.exponential(1000)` produces: `1000, 2000, 4000, 8000, ...`
|
|
31
|
+
- with `maxDelay: 5000` actual waits become: `1000, 2000, 4000, 5000, ...`
|
package/dist/index.cjs
CHANGED
|
@@ -14,7 +14,9 @@ const retry = async (func, ...settings) => {
|
|
|
14
14
|
return await func();
|
|
15
15
|
} catch (error) {
|
|
16
16
|
if (mergedSettings && mergedSettings.retryCondition(error) && retries < mergedSettings.maxRetries) {
|
|
17
|
-
|
|
17
|
+
const computedDelay = mergedSettings.interval(retries + 1);
|
|
18
|
+
const delay = mergedSettings.maxDelay !== void 0 ? Math.min(computedDelay, mergedSettings.maxDelay) : computedDelay;
|
|
19
|
+
await wait(delay);
|
|
18
20
|
return makeCall(retries + 1);
|
|
19
21
|
}
|
|
20
22
|
throw error;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["// biome-ignore lint/suspicious/noExplicitAny: Errors can be anything\nexport type RetryConditionFunction = (error: any) => boolean\n\nexport interface RetrySettings {\n maxRetries: number\n interval: (retryCount: number) => number\n retryCondition?: RetryConditionFunction\n maxDelay?: number\n}\n\nconst defaultSettings: Partial<RetrySettings> = {\n retryCondition: () => true,\n}\nconst mergeSettings = (\n ...settings: Array<RetrySettings | undefined>\n): RetrySettings | undefined => {\n // remove undefineds, return if no settings passed\n const s = settings.filter((s) => s) as RetrySettings[]\n if (!s.length) return undefined\n\n // merge settings with priority to the latest\n const merged: RetrySettings = Object.assign({}, defaultSettings, ...s)\n\n return merged\n}\n\nconst wait = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(() => resolve(), ms))\n\nexport const retry = async <T>(\n func: () => Promise<T>,\n ...settings: Array<RetrySettings | undefined>\n): Promise<T> => {\n const mergedSettings = mergeSettings(...settings)\n\n const makeCall = async <T>(retries = 0): Promise<T> => {\n try {\n const response = await func()\n return response as T\n } catch (error) {\n if (\n mergedSettings &&\n // biome-ignore lint/style/noNonNullAssertion: Will be set by defaultSettings\n mergedSettings.retryCondition!(error) &&\n retries < mergedSettings.maxRetries\n ) {\n
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["// biome-ignore lint/suspicious/noExplicitAny: Errors can be anything\nexport type RetryConditionFunction = (error: any) => boolean\n\nexport interface RetrySettings {\n maxRetries: number\n interval: (retryCount: number) => number\n retryCondition?: RetryConditionFunction\n maxDelay?: number\n}\n\nconst defaultSettings: Partial<RetrySettings> = {\n retryCondition: () => true,\n}\nconst mergeSettings = (\n ...settings: Array<RetrySettings | undefined>\n): RetrySettings | undefined => {\n // remove undefineds, return if no settings passed\n const s = settings.filter((s) => s) as RetrySettings[]\n if (!s.length) return undefined\n\n // merge settings with priority to the latest\n const merged: RetrySettings = Object.assign({}, defaultSettings, ...s)\n\n return merged\n}\n\nconst wait = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(() => resolve(), ms))\n\nexport const retry = async <T>(\n func: () => Promise<T>,\n ...settings: Array<RetrySettings | undefined>\n): Promise<T> => {\n const mergedSettings = mergeSettings(...settings)\n\n const makeCall = async <T>(retries = 0): Promise<T> => {\n try {\n const response = await func()\n return response as T\n } catch (error) {\n if (\n mergedSettings &&\n // biome-ignore lint/style/noNonNullAssertion: Will be set by defaultSettings\n mergedSettings.retryCondition!(error) &&\n retries < mergedSettings.maxRetries\n ) {\n const computedDelay = mergedSettings.interval(retries + 1)\n const delay =\n mergedSettings.maxDelay !== undefined\n ? Math.min(computedDelay, mergedSettings.maxDelay)\n : computedDelay\n await wait(delay)\n return makeCall(retries + 1)\n }\n\n throw error\n }\n }\n return makeCall(0)\n}\n\nexport const interval = {\n fixed: (delay: number) => (_retries: number) => delay,\n linear: (delay: number) => (retries: number) => retries * delay,\n exponential:\n (delay: number, base = 2) =>\n (retries: number) =>\n base ** (retries - 1) * delay,\n}\n\nconst clientErrorCodes = [\n 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414,\n 415, 416, 417, 418, 421, 422, 423, 424, 425, 426, 428, 429, 431, 451,\n] as const\ntype ClientErrorCode = (typeof clientErrorCodes)[number]\n\nconst serverErrorCodes = [\n 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511,\n] as const\ntype ServerErrorCode = (typeof serverErrorCodes)[number]\n\ntype ErrorType = 'server' | 'client' | ServerErrorCode | ClientErrorCode\n\ntype HttpError = {\n status?: number\n statusCode?: number\n response?: {\n status?: number\n statusCode?: number\n }\n}\n\nexport const retryCondition = {\n always: (_error: unknown) => true,\n httpErrors:\n (...codes: ErrorType[]) =>\n (error: HttpError) => {\n const captureCodes = codes\n .flatMap((code) => {\n if (typeof code === 'number') return code\n /* istanbul ignore else */\n if (code === 'client') return clientErrorCodes\n /* istanbul ignore next */\n if (code === 'server') return serverErrorCodes\n /* istanbul ignore next */\n return 500\n })\n .filter((code) => !!code) as number[]\n const status =\n error.status ||\n error.statusCode ||\n error.response?.status ||\n error.response?.statusCode ||\n 0\n return captureCodes.includes(status)\n },\n serverErrors: (error: HttpError) =>\n retryCondition.httpErrors('server')(error),\n // biome-ignore lint/suspicious/noExplicitAny: Errors can be anything\n custom: (func: (error: any) => boolean) => func,\n}\n"],"mappings":";;AAUA,MAAM,kBAA0C,EAC9C,sBAAsB,KACxB;AACA,MAAM,iBACJ,GAAG,aAC2B;CAE9B,MAAM,IAAI,SAAS,QAAQ,MAAM,CAAC;CAClC,IAAI,CAAC,EAAE,QAAQ,OAAO,KAAA;CAKtB,OAF8B,OAAO,OAAO,CAAC,GAAG,iBAAiB,GAAG,CAExD;AACd;AAEA,MAAM,QAAQ,OACZ,IAAI,SAAe,YAAY,iBAAiB,QAAQ,GAAG,EAAE,CAAC;AAEhE,MAAa,QAAQ,OACnB,MACA,GAAG,aACY;CACf,MAAM,iBAAiB,cAAc,GAAG,QAAQ;CAEhD,MAAM,WAAW,OAAU,UAAU,MAAkB;EACrD,IAAI;GAEF,OAAO,MADgB,KAAK;EAE9B,SAAS,OAAO;GACd,IACE,kBAEA,eAAe,eAAgB,KAAK,KACpC,UAAU,eAAe,YACzB;IACA,MAAM,gBAAgB,eAAe,SAAS,UAAU,CAAC;IACzD,MAAM,QACJ,eAAe,aAAa,KAAA,IACxB,KAAK,IAAI,eAAe,eAAe,QAAQ,IAC/C;IACN,MAAM,KAAK,KAAK;IAChB,OAAO,SAAS,UAAU,CAAC;GAC7B;GAEA,MAAM;EACR;CACF;CACA,OAAO,SAAS,CAAC;AACnB;AAEA,MAAa,WAAW;CACtB,QAAQ,WAAmB,aAAqB;CAChD,SAAS,WAAmB,YAAoB,UAAU;CAC1D,cACG,OAAe,OAAO,OACtB,YACC,SAAS,UAAU,KAAK;AAC9B;AAEA,MAAM,mBAAmB;CACvB;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CACtE;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AACnE;AAGA,MAAM,mBAAmB;CACvB;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AACpD;AAcA,MAAa,iBAAiB;CAC5B,SAAS,WAAoB;CAC7B,aACG,GAAG,WACH,UAAqB;EACpB,MAAM,eAAe,MAClB,SAAS,SAAS;GACjB,IAAI,OAAO,SAAS,UAAU,OAAO;;GAErC,IAAI,SAAS,UAAU,OAAO;;GAE9B,IAAI,SAAS,UAAU,OAAO;;GAE9B,OAAO;EACT,CAAC,CAAC,CACD,QAAQ,SAAS,CAAC,CAAC,IAAI;EAC1B,MAAM,SACJ,MAAM,UACN,MAAM,cACN,MAAM,UAAU,UAChB,MAAM,UAAU,cAChB;EACF,OAAO,aAAa,SAAS,MAAM;CACrC;CACF,eAAe,UACb,eAAe,WAAW,QAAQ,CAAC,CAAC,KAAK;CAE3C,SAAS,SAAkC;AAC7C"}
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/retry.ts"],"mappings":";KACY,
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/retry.ts"],"mappings":";KACY,0BAA0B;UAErB;EACf;EACA,WAAW;EACX,iBAAiB;EACjB;;cAsBW,QAAe,GAC1B,YAAY,QAAQ,OACjB,UAAU,MAAM,+BAClB,QAAQ;cA6BE;EACI,QAAA,mBAAY;EACX,SAAA,mBAAY;EAElB,cAAA,eAAM,mBACb;;cAIC;KAID,0BAA0B;cAEzB;KAGD,0BAA0B;KAE1B,kCAAkC,kBAAkB;KAEpD;EACH;EACA;EACA;IACE;IACA;;;cAIS;EACM,SAAA;EAEJ,gBAAA,OAAA,iBACV,OAAO;EAoBY,eAAA,OAAA;EAGP,SAAA,OAAC,4BAAsB"}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/retry.ts"],"mappings":";KACY,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/retry.ts"],"mappings":";KACY,0BAA0B;UAErB;EACf;EACA,WAAW;EACX,iBAAiB;EACjB;;cAsBW,QAAe,GAC1B,YAAY,QAAQ,OACjB,UAAU,MAAM,+BAClB,QAAQ;cA6BE;EACI,QAAA,mBAAY;EACX,SAAA,mBAAY;EAElB,cAAA,eAAM,mBACb;;cAIC;KAID,0BAA0B;cAEzB;KAGD,0BAA0B;KAE1B,kCAAkC,kBAAkB;KAEpD;EACH;EACA;EACA;IACE;IACA;;;cAIS;EACM,SAAA;EAEJ,gBAAA,OAAA,iBACV,OAAO;EAoBY,eAAA,OAAA;EAGP,SAAA,OAAC,4BAAsB"}
|
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,9 @@ const retry = async (func, ...settings) => {
|
|
|
13
13
|
return await func();
|
|
14
14
|
} catch (error) {
|
|
15
15
|
if (mergedSettings && mergedSettings.retryCondition(error) && retries < mergedSettings.maxRetries) {
|
|
16
|
-
|
|
16
|
+
const computedDelay = mergedSettings.interval(retries + 1);
|
|
17
|
+
const delay = mergedSettings.maxDelay !== void 0 ? Math.min(computedDelay, mergedSettings.maxDelay) : computedDelay;
|
|
18
|
+
await wait(delay);
|
|
17
19
|
return makeCall(retries + 1);
|
|
18
20
|
}
|
|
19
21
|
throw error;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["// biome-ignore lint/suspicious/noExplicitAny: Errors can be anything\nexport type RetryConditionFunction = (error: any) => boolean\n\nexport interface RetrySettings {\n maxRetries: number\n interval: (retryCount: number) => number\n retryCondition?: RetryConditionFunction\n maxDelay?: number\n}\n\nconst defaultSettings: Partial<RetrySettings> = {\n retryCondition: () => true,\n}\nconst mergeSettings = (\n ...settings: Array<RetrySettings | undefined>\n): RetrySettings | undefined => {\n // remove undefineds, return if no settings passed\n const s = settings.filter((s) => s) as RetrySettings[]\n if (!s.length) return undefined\n\n // merge settings with priority to the latest\n const merged: RetrySettings = Object.assign({}, defaultSettings, ...s)\n\n return merged\n}\n\nconst wait = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(() => resolve(), ms))\n\nexport const retry = async <T>(\n func: () => Promise<T>,\n ...settings: Array<RetrySettings | undefined>\n): Promise<T> => {\n const mergedSettings = mergeSettings(...settings)\n\n const makeCall = async <T>(retries = 0): Promise<T> => {\n try {\n const response = await func()\n return response as T\n } catch (error) {\n if (\n mergedSettings &&\n // biome-ignore lint/style/noNonNullAssertion: Will be set by defaultSettings\n mergedSettings.retryCondition!(error) &&\n retries < mergedSettings.maxRetries\n ) {\n
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/retry.ts"],"sourcesContent":["// biome-ignore lint/suspicious/noExplicitAny: Errors can be anything\nexport type RetryConditionFunction = (error: any) => boolean\n\nexport interface RetrySettings {\n maxRetries: number\n interval: (retryCount: number) => number\n retryCondition?: RetryConditionFunction\n maxDelay?: number\n}\n\nconst defaultSettings: Partial<RetrySettings> = {\n retryCondition: () => true,\n}\nconst mergeSettings = (\n ...settings: Array<RetrySettings | undefined>\n): RetrySettings | undefined => {\n // remove undefineds, return if no settings passed\n const s = settings.filter((s) => s) as RetrySettings[]\n if (!s.length) return undefined\n\n // merge settings with priority to the latest\n const merged: RetrySettings = Object.assign({}, defaultSettings, ...s)\n\n return merged\n}\n\nconst wait = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(() => resolve(), ms))\n\nexport const retry = async <T>(\n func: () => Promise<T>,\n ...settings: Array<RetrySettings | undefined>\n): Promise<T> => {\n const mergedSettings = mergeSettings(...settings)\n\n const makeCall = async <T>(retries = 0): Promise<T> => {\n try {\n const response = await func()\n return response as T\n } catch (error) {\n if (\n mergedSettings &&\n // biome-ignore lint/style/noNonNullAssertion: Will be set by defaultSettings\n mergedSettings.retryCondition!(error) &&\n retries < mergedSettings.maxRetries\n ) {\n const computedDelay = mergedSettings.interval(retries + 1)\n const delay =\n mergedSettings.maxDelay !== undefined\n ? Math.min(computedDelay, mergedSettings.maxDelay)\n : computedDelay\n await wait(delay)\n return makeCall(retries + 1)\n }\n\n throw error\n }\n }\n return makeCall(0)\n}\n\nexport const interval = {\n fixed: (delay: number) => (_retries: number) => delay,\n linear: (delay: number) => (retries: number) => retries * delay,\n exponential:\n (delay: number, base = 2) =>\n (retries: number) =>\n base ** (retries - 1) * delay,\n}\n\nconst clientErrorCodes = [\n 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414,\n 415, 416, 417, 418, 421, 422, 423, 424, 425, 426, 428, 429, 431, 451,\n] as const\ntype ClientErrorCode = (typeof clientErrorCodes)[number]\n\nconst serverErrorCodes = [\n 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511,\n] as const\ntype ServerErrorCode = (typeof serverErrorCodes)[number]\n\ntype ErrorType = 'server' | 'client' | ServerErrorCode | ClientErrorCode\n\ntype HttpError = {\n status?: number\n statusCode?: number\n response?: {\n status?: number\n statusCode?: number\n }\n}\n\nexport const retryCondition = {\n always: (_error: unknown) => true,\n httpErrors:\n (...codes: ErrorType[]) =>\n (error: HttpError) => {\n const captureCodes = codes\n .flatMap((code) => {\n if (typeof code === 'number') return code\n /* istanbul ignore else */\n if (code === 'client') return clientErrorCodes\n /* istanbul ignore next */\n if (code === 'server') return serverErrorCodes\n /* istanbul ignore next */\n return 500\n })\n .filter((code) => !!code) as number[]\n const status =\n error.status ||\n error.statusCode ||\n error.response?.status ||\n error.response?.statusCode ||\n 0\n return captureCodes.includes(status)\n },\n serverErrors: (error: HttpError) =>\n retryCondition.httpErrors('server')(error),\n // biome-ignore lint/suspicious/noExplicitAny: Errors can be anything\n custom: (func: (error: any) => boolean) => func,\n}\n"],"mappings":";AAUA,MAAM,kBAA0C,EAC9C,sBAAsB,KACxB;AACA,MAAM,iBACJ,GAAG,aAC2B;CAE9B,MAAM,IAAI,SAAS,QAAQ,MAAM,CAAC;CAClC,IAAI,CAAC,EAAE,QAAQ,OAAO,KAAA;CAKtB,OAF8B,OAAO,OAAO,CAAC,GAAG,iBAAiB,GAAG,CAExD;AACd;AAEA,MAAM,QAAQ,OACZ,IAAI,SAAe,YAAY,iBAAiB,QAAQ,GAAG,EAAE,CAAC;AAEhE,MAAa,QAAQ,OACnB,MACA,GAAG,aACY;CACf,MAAM,iBAAiB,cAAc,GAAG,QAAQ;CAEhD,MAAM,WAAW,OAAU,UAAU,MAAkB;EACrD,IAAI;GAEF,OAAO,MADgB,KAAK;EAE9B,SAAS,OAAO;GACd,IACE,kBAEA,eAAe,eAAgB,KAAK,KACpC,UAAU,eAAe,YACzB;IACA,MAAM,gBAAgB,eAAe,SAAS,UAAU,CAAC;IACzD,MAAM,QACJ,eAAe,aAAa,KAAA,IACxB,KAAK,IAAI,eAAe,eAAe,QAAQ,IAC/C;IACN,MAAM,KAAK,KAAK;IAChB,OAAO,SAAS,UAAU,CAAC;GAC7B;GAEA,MAAM;EACR;CACF;CACA,OAAO,SAAS,CAAC;AACnB;AAEA,MAAa,WAAW;CACtB,QAAQ,WAAmB,aAAqB;CAChD,SAAS,WAAmB,YAAoB,UAAU;CAC1D,cACG,OAAe,OAAO,OACtB,YACC,SAAS,UAAU,KAAK;AAC9B;AAEA,MAAM,mBAAmB;CACvB;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CACtE;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AACnE;AAGA,MAAM,mBAAmB;CACvB;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AACpD;AAcA,MAAa,iBAAiB;CAC5B,SAAS,WAAoB;CAC7B,aACG,GAAG,WACH,UAAqB;EACpB,MAAM,eAAe,MAClB,SAAS,SAAS;GACjB,IAAI,OAAO,SAAS,UAAU,OAAO;;GAErC,IAAI,SAAS,UAAU,OAAO;;GAE9B,IAAI,SAAS,UAAU,OAAO;;GAE9B,OAAO;EACT,CAAC,CAAC,CACD,QAAQ,SAAS,CAAC,CAAC,IAAI;EAC1B,MAAM,SACJ,MAAM,UACN,MAAM,cACN,MAAM,UAAU,UAChB,MAAM,UAAU,cAChB;EACF,OAAO,aAAa,SAAS,MAAM;CACrC;CACF,eAAe,UACb,eAAe,WAAW,QAAQ,CAAC,CAAC,KAAK;CAE3C,SAAS,SAAkC;AAC7C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sebspark/retry",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"typecheck": "vitest --typecheck.only --passWithNoTests"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"axios": "1.
|
|
31
|
+
"axios": "1.19.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|