jitterbug 1.0.0-alpha.4 → 1.0.0-alpha.6
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 +64 -86
- package/dist/index.cjs +55 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +55 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Jitterbug is a lightweight library for building reliable retry behavior in distributed systems and API clients.
|
|
1
|
+

|
|
4
2
|
|
|
5
3
|

|
|
6
4
|

|
|
7
|
-
](https://www.npmjs.com/package/jitterbug)
|
|
8
6
|

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Jitterbug is a modern, type‑safe retry engine for Node.js and browser environments. It provides predictable backoff behavior and a suite of configurable jitter strategies, all wrapped in a clean, minimal API. The library is intentionally lightweight and dependency‑free to keep integration simple and reduce risk for consumers.
|
|
11
|
+
|
|
12
|
+
Designed to be framework‑agnostic and easy to adopt, Jitterbug emphasizes clarity, reliability, and maintainability. Its implementation is backed by comprehensive automated testing to ensure consistent behavior and to support confident contributions from the community.
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
|
|
10
16
|
## Installation
|
|
11
17
|
|
|
@@ -35,7 +41,8 @@ const fetchWithRetry = retry(async (url) => {
|
|
|
35
41
|
}, {
|
|
36
42
|
maxAttempts: 5,
|
|
37
43
|
delay: 1000,
|
|
38
|
-
backoff: 'exponential',
|
|
44
|
+
backoff: 'exponential',
|
|
45
|
+
jitterConfig: { type: 'equal' }, // optional jitter
|
|
39
46
|
onRetry: (error, attempt, waitTime) => {
|
|
40
47
|
console.log(`Retry attempt ${attempt} after ${waitTime}ms`);
|
|
41
48
|
}
|
|
@@ -50,6 +57,43 @@ try {
|
|
|
50
57
|
}
|
|
51
58
|
```
|
|
52
59
|
|
|
60
|
+
### Jitter Utility Functions
|
|
61
|
+
|
|
62
|
+
Jitterbug also exports its jitter calculation helpers directly.
|
|
63
|
+
These functions are pure, deterministic (when `Math.random` is mocked), and can be used independently of the retry system.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
#### **`calculateEqualJitter(baseDelayMs: number): number`**
|
|
68
|
+
Produces a delay between **50% and 100%** of the base delay.
|
|
69
|
+
Useful for predictable but desynchronized retry timing.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
#### **`calculateFullJitter(minDelayMs: number, maxDelayMs: number): number`**
|
|
74
|
+
Returns a completely random delay between `minDelayMs` (inclusive) and `maxDelayMs` (exclusive).
|
|
75
|
+
Ideal for aggressively spreading retries under heavy load.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
#### **`calculateFixedJitter(baseDelayMs: number, jitterAmount: number): number`**
|
|
80
|
+
Subtracts a constant amount from the base delay, never below zero.
|
|
81
|
+
Useful when you want consistent desynchronization without randomness.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
#### **`calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number`**
|
|
86
|
+
Applies a symmetric ±fraction jitter around the base delay.
|
|
87
|
+
Example: `fraction = 0.2` → delay may vary between 80% and 120% of the base.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
#### **`calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs?: number): number`**
|
|
92
|
+
Implements AWS‑style decorrelated jitter.
|
|
93
|
+
Each retry picks a random delay between the base delay and **3× the previous delay**, capped at `maxDelayMs`.
|
|
94
|
+
Excellent for large distributed systems where synchronized retries can overwhelm downstream services.
|
|
95
|
+
|
|
96
|
+
|
|
53
97
|
## API
|
|
54
98
|
|
|
55
99
|
### `retry<T>(fn: T, options?: RetryOptions): T`
|
|
@@ -62,6 +106,7 @@ Creates a retry wrapper function.
|
|
|
62
106
|
- `maxAttempts` (number, default: 3): Maximum number of retry attempts
|
|
63
107
|
- `delay` (number, default: 1000): Base delay in milliseconds
|
|
64
108
|
- `backoff` ('exponential' | 'linear' | 'fixed', default: 'exponential'): Backoff strategy
|
|
109
|
+
- `jitterConfig` (`JitterConfig` | `undefined`): Optional jitter strategy applied on top of the base dela
|
|
65
110
|
- `onRetry` ((error: Error, attempt: number, waitTime: number) => void, optional): Callback called before each retry
|
|
66
111
|
|
|
67
112
|
**Returns:** A function that wraps the original function with retry logic
|
|
@@ -71,93 +116,26 @@ Creates a retry wrapper function.
|
|
|
71
116
|
import type { RetryOptions, BackoffStrategy } from 'jitterbug';
|
|
72
117
|
```
|
|
73
118
|
|
|
74
|
-
|
|
119
|
+
### Jitter Configuration Options
|
|
75
120
|
|
|
76
|
-
|
|
121
|
+
- **`{ type: 'none' }`**
|
|
122
|
+
No jitter applied. Retries use the exact backoff delay.
|
|
77
123
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
npm run build
|
|
124
|
+
- **`{ type: 'equal' }`**
|
|
125
|
+
Splits the delay into a fixed half and a random half. Produces predictable but desynchronized retry timing.
|
|
81
126
|
|
|
82
|
-
|
|
83
|
-
|
|
127
|
+
- **`{ type: 'full', min, max }`**
|
|
128
|
+
Picks a completely random delay between `min` and `max`. Best for aggressively spreading retries under heavy load.
|
|
84
129
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
```
|
|
130
|
+
- **`{ type: 'fixed', amount }`**
|
|
131
|
+
Subtracts a constant amount from the base delay (never below zero). Useful for consistent, predictable desynchronization.
|
|
88
132
|
|
|
89
|
-
|
|
133
|
+
- **`{ type: 'random', fraction }`**
|
|
134
|
+
Applies a symmetric ±fraction jitter around the base delay (e.g., `0.2` = ±20%). Light, centered randomness.
|
|
90
135
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
npm test
|
|
94
|
-
|
|
95
|
-
# Run tests once
|
|
96
|
-
npm run test:run
|
|
97
|
-
|
|
98
|
-
# Run tests with coverage
|
|
99
|
-
npm run test:coverage
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Publishing
|
|
103
|
-
|
|
104
|
-
Publishing to npm is automated via GitHub Actions. To publish a new version:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
# Choose one based on the type of change:
|
|
108
|
-
npm version patch # Bug fixes: 0.1.0 -> 0.1.1
|
|
109
|
-
npm version minor # New features: 0.1.0 -> 0.2.0
|
|
110
|
-
npm version major # Breaking changes: 0.1.0 -> 1.0.0
|
|
111
|
-
|
|
112
|
-
# This automatically:
|
|
113
|
-
# - Updates package.json version
|
|
114
|
-
# - Builds the project (via version script)
|
|
115
|
-
# - Creates a git commit
|
|
116
|
-
# - Creates a git tag (v0.1.1, v0.2.0, etc.)
|
|
117
|
-
|
|
118
|
-
# Then push both the commit and tags:
|
|
119
|
-
git push && git push --tags
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**Publishing alpha/beta versions:**
|
|
123
|
-
|
|
124
|
-
For pre-release versions (alpha, beta, rc):
|
|
136
|
+
- **`{ type: 'decorrelated', maxDelay }`**
|
|
137
|
+
AWS‑style decorrelated jitter. Each retry picks a random delay between the base delay and 3× the previous jittered delay, capped at `maxDelay`.
|
|
125
138
|
|
|
126
|
-
```bash
|
|
127
|
-
# Create an alpha version
|
|
128
|
-
npm version prepatch --preid=alpha # 0.1.0 -> 0.1.1-alpha.0
|
|
129
|
-
npm version preminor --preid=alpha # 0.1.0 -> 0.2.0-alpha.0
|
|
130
|
-
npm version premajor --preid=alpha # 0.1.0 -> 1.0.0-alpha.0
|
|
131
|
-
|
|
132
|
-
# Increment alpha version
|
|
133
|
-
npm version prerelease --preid=alpha # 0.1.1-alpha.0 -> 0.1.1-alpha.1
|
|
134
|
-
|
|
135
|
-
# Push the commit and tag
|
|
136
|
-
git push && git push --tags
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
The CI/CD pipeline will automatically:
|
|
140
|
-
- Run all tests
|
|
141
|
-
- Build the project
|
|
142
|
-
- Verify version matches the tag
|
|
143
|
-
- Publish to npm (if tests pass)
|
|
144
|
-
|
|
145
|
-
**Note:** `NPM_TOKEN` secret needs to be setupas a GitHub secret with your npm access token.
|
|
146
|
-
|
|
147
|
-
### Project Structure
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
jitterbug/
|
|
151
|
-
├── src/ # TypeScript source code
|
|
152
|
-
│ ├── retry.ts # Core retry implementation
|
|
153
|
-
│ └── index.ts # Main entry point
|
|
154
|
-
├── dist/ # Built output (ESM + CJS + types)
|
|
155
|
-
├── test/ # Test files
|
|
156
|
-
│ └── retry.test.js
|
|
157
|
-
├── tsconfig.json # TypeScript configuration
|
|
158
|
-
├── tsup.config.ts # Build configuration
|
|
159
|
-
└── package.json
|
|
160
|
-
```
|
|
161
139
|
|
|
162
140
|
## Test Coverage
|
|
163
141
|
|
|
@@ -166,7 +144,7 @@ jitterbug/
|
|
|
166
144
|
| Metric | Coverage |
|
|
167
145
|
|--------|----------|
|
|
168
146
|
| Statements | 100.00% |
|
|
169
|
-
| Branches |
|
|
147
|
+
| Branches | 96.67% |
|
|
170
148
|
| Functions | 100.00% |
|
|
171
149
|
| Lines | 100.00% |
|
|
172
150
|
|
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,35 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/jitter.ts
|
|
12
|
+
var jitter_exports = {};
|
|
13
|
+
__export(jitter_exports, {
|
|
14
|
+
calculateDecorrelatedJitter: () => calculateDecorrelatedJitter,
|
|
15
|
+
calculateEqualJitter: () => calculateEqualJitter,
|
|
16
|
+
calculateFixedJitter: () => calculateFixedJitter,
|
|
17
|
+
calculateFullJitter: () => calculateFullJitter,
|
|
18
|
+
calculateRandomJitter: () => calculateRandomJitter
|
|
19
|
+
});
|
|
20
|
+
function calculateFullJitter(minDelayMs, maxDelayMs) {
|
|
21
|
+
return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;
|
|
22
|
+
}
|
|
23
|
+
function calculateEqualJitter(baseDelayMs) {
|
|
24
|
+
const halfDelay = baseDelayMs / 2;
|
|
25
|
+
return halfDelay + Math.random() * halfDelay;
|
|
26
|
+
}
|
|
27
|
+
function calculateFixedJitter(maxDelayMs, jitterAmount) {
|
|
28
|
+
return Math.max(0, maxDelayMs - jitterAmount);
|
|
29
|
+
}
|
|
30
|
+
function calculateRandomJitter(baseDelayMs, jitterFraction) {
|
|
31
|
+
const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;
|
|
32
|
+
return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));
|
|
33
|
+
}
|
|
34
|
+
function calculateDecorrelatedJitter(baseDelayMs, maxDelayMs, prevDelayMs = 0) {
|
|
35
|
+
const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);
|
|
36
|
+
const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);
|
|
37
|
+
return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay);
|
|
38
|
+
}
|
|
39
|
+
|
|
11
40
|
// src/retry.ts
|
|
12
41
|
function sleep(ms) {
|
|
13
42
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -23,16 +52,37 @@ function calculateDelay(baseDelay, attempt, backoff) {
|
|
|
23
52
|
return baseDelay;
|
|
24
53
|
}
|
|
25
54
|
}
|
|
55
|
+
function applyJitter(baseDelay, jitter, prevDelay) {
|
|
56
|
+
if (!jitter || jitter.type === "none") return 0;
|
|
57
|
+
switch (jitter.type) {
|
|
58
|
+
case "equal":
|
|
59
|
+
return calculateEqualJitter(baseDelay);
|
|
60
|
+
case "full":
|
|
61
|
+
return calculateFullJitter(jitter.min, jitter.max);
|
|
62
|
+
case "fixed":
|
|
63
|
+
return calculateFixedJitter(baseDelay, jitter.amount);
|
|
64
|
+
case "random":
|
|
65
|
+
return calculateRandomJitter(baseDelay, jitter.fraction);
|
|
66
|
+
case "decorrelated":
|
|
67
|
+
return calculateDecorrelatedJitter(
|
|
68
|
+
baseDelay,
|
|
69
|
+
jitter.maxDelay,
|
|
70
|
+
prevDelay
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
26
74
|
function retry(fn, options = {}) {
|
|
27
75
|
const {
|
|
28
76
|
maxAttempts = 3,
|
|
29
77
|
delay = 1e3,
|
|
30
78
|
backoff = "exponential",
|
|
79
|
+
jitterConfig,
|
|
31
80
|
onRetry = () => {
|
|
32
81
|
}
|
|
33
82
|
} = options;
|
|
34
83
|
return (async function(...args) {
|
|
35
84
|
let lastError;
|
|
85
|
+
let prevJitterDelay = 0;
|
|
36
86
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
37
87
|
try {
|
|
38
88
|
return await fn(...args);
|
|
@@ -40,8 +90,11 @@ function retry(fn, options = {}) {
|
|
|
40
90
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
41
91
|
if (attempt < maxAttempts) {
|
|
42
92
|
const waitTime = calculateDelay(delay, attempt, backoff);
|
|
43
|
-
|
|
44
|
-
|
|
93
|
+
const jitterMs = applyJitter(waitTime, jitterConfig, prevJitterDelay);
|
|
94
|
+
prevJitterDelay = jitterMs;
|
|
95
|
+
const delayTimeMs = waitTime + jitterMs;
|
|
96
|
+
onRetry(lastError, attempt, delayTimeMs);
|
|
97
|
+
await sleep(delayTimeMs);
|
|
45
98
|
}
|
|
46
99
|
}
|
|
47
100
|
}
|
|
@@ -49,35 +102,6 @@ function retry(fn, options = {}) {
|
|
|
49
102
|
});
|
|
50
103
|
}
|
|
51
104
|
|
|
52
|
-
// src/jitter.ts
|
|
53
|
-
var jitter_exports = {};
|
|
54
|
-
__export(jitter_exports, {
|
|
55
|
-
calculateDecorrelatedJitter: () => calculateDecorrelatedJitter,
|
|
56
|
-
calculateEqualJitter: () => calculateEqualJitter,
|
|
57
|
-
calculateFixedJitter: () => calculateFixedJitter,
|
|
58
|
-
calculateFullJitter: () => calculateFullJitter,
|
|
59
|
-
calculateRandomJitter: () => calculateRandomJitter
|
|
60
|
-
});
|
|
61
|
-
function calculateFullJitter(minDelayMs, maxDelayMs) {
|
|
62
|
-
return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;
|
|
63
|
-
}
|
|
64
|
-
function calculateEqualJitter(baseDelayMs) {
|
|
65
|
-
const halfDelay = baseDelayMs / 2;
|
|
66
|
-
return halfDelay + Math.random() * halfDelay;
|
|
67
|
-
}
|
|
68
|
-
function calculateFixedJitter(maxDelayMs, jitterAmount) {
|
|
69
|
-
return Math.max(0, maxDelayMs - jitterAmount);
|
|
70
|
-
}
|
|
71
|
-
function calculateRandomJitter(baseDelayMs, jitterFraction) {
|
|
72
|
-
const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;
|
|
73
|
-
return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));
|
|
74
|
-
}
|
|
75
|
-
function calculateDecorrelatedJitter(baseDelayMs, maxDelayMs, prevDelayMs = 0) {
|
|
76
|
-
const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);
|
|
77
|
-
const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);
|
|
78
|
-
return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
105
|
// src/index.ts
|
|
82
106
|
var index_default = {
|
|
83
107
|
retry,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/jitter.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Core retry logic implementation\n */\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number {\n switch (backoff) {\n case 'exponential':\n return baseDelay * Math.pow(2, attempt - 1);\n case 'linear':\n return baseDelay * attempt;\n case 'fixed':\n default:\n return baseDelay;\n }\n}\n\nexport type BackoffStrategy = 'exponential' | 'linear' | 'fixed';\n\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxAttempts?: number;\n /** Base delay in milliseconds (default: 1000) */\n delay?: number;\n /** Backoff strategy: 'exponential', 'linear', or 'fixed' (default: 'exponential') */\n backoff?: BackoffStrategy;\n /** Callback called before each retry (error, attempt, waitTime) */\n onRetry?: (error: Error, attempt: number, waitTime: number) => void;\n}\n\n/**\n * Creates a retry wrapper function\n * @param fn - The async function to retry\n * @param options - Retry configuration\n * @returns A function that wraps the original function with retry logic\n */\n// @typescript-eslint/no-explicit-any\nexport function retry<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n options: RetryOptions = {}\n): T {\n const {\n maxAttempts = 3,\n delay = 1000,\n backoff = 'exponential',\n onRetry = () => {}\n } = options;\n\n return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {\n let lastError: Error;\n \n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn(...args) as ReturnType<T>;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n \n if (attempt < maxAttempts) {\n const waitTime = calculateDelay(delay, attempt, backoff);\n onRetry(lastError, attempt, waitTime);\n await sleep(waitTime);\n }\n }\n }\n \n throw lastError!;\n }) as T;\n}\n\nexport { calculateDelay, sleep };\n\n","/**\n * Generates a full‑jitter delay within the given range.\n *\n * Full jitter selects a completely random delay between `minDelay` and\n * `maxDelay`, spreading numbers across the entire interval to create jitter.\n *\n * @param {number} minDelayMs - The minimum delay (inclusive).\n * @param {number} maxDelayMs - The maximum delay (exclusive).\n * @returns {number} A random delay between `minDelay` and `maxDelay`.\n * @description Full jitter is ideal when many clients may retry at the same time and you need to aggressively spread those retries out. It’s especially useful under high contention or bursty failure conditions, where a wide range of random delays helps prevent retry storms and reduces load on downstream services.\n */\nexport function calculateFullJitter(minDelayMs: number, maxDelayMs: number): number {\n return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;\n}\n\n/**\n * Calculates an equal‑jitter backoff delay.\n *\n * Equal jitter splits the delay into a fixed half and a random half.\n * This reduces synchronization between clients while avoiding the\n * extremely low delays produced by full jitter.\n *\n * @param {number} baseDelayMs - The original backoff delay before jitter.\n * @returns {number} A delay between half of `baseDelay` and the full `baseDelay`.\n * @description Equal jitter is useful when you want a smoother, more predictable retry pattern that still avoids synchronized retries. It guarantees at least half of the base delay while adding randomness to the other half, giving you a balanced backoff that reduces contention without the extreme variability of full jitter.\n */\nexport function calculateEqualJitter(baseDelayMs: number): number {\n const halfDelay = baseDelayMs / 2;\n return halfDelay + Math.random() * halfDelay;\n}\n\n\n/**\n * Calculates a fixed‑jitter delay by subtracting a constant amount from the base delay. Unlike random jitter strategies, fixed jitter provides predictable timing while still preventing perfectly aligned retries.\n * Use this when you want a small, consistent desynchronization between clients without introducing randomness — ideal for stable systems where timing consistency matters more than wide jitter distribution.\n * @param {number} maxDelayMs - The original delay before applying jitter.\n * @param {number} jitterAmount - The fixed amount to subtract.\n * @returns {number} The delay after subtracting `jitterAmount`, never below zero.\n */\nexport function calculateFixedJitter(maxDelayMs: number, jitterAmount: number): number {\n return Math.max(0, maxDelayMs - jitterAmount);\n}\n\n/**\n * Calculates a random‑jitter backoff delay by applying a symmetric\n * random variation around the base delay. The jitter fraction determines\n * how far the delay may swing above or below the original value.\n *\n * Random jitter is useful when you want to “wiggle” the delay without\n * dramatically changing its overall shape. It provides light\n * desynchronization between clients while keeping the delay centered\n * around the base value — ideal for systems that need some variability\n * but not the wide spread of full jitter.\n *\n * @param {number} baseDelayMs - The original delay before jitter is applied.\n * @param {number} jitterFraction - Maximum fractional deviation (e.g., 0.2 for ±20%).\n * @returns {number} A jittered delay, clamped to zero if the calculation goes negative.\n */\nexport function calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number {\n // Math.random() gives us 0 to 1 so multiply by 2 and subtract 1 to get -1 to 1 range\n const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;\n return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));\n}\n\n/**\n * Calculates a decorrelated jitter backoff delay using the algorithm\n * popularized by AWS. Each retry delay is chosen randomly between the\n * base delay and three times the previous delay, then capped at the\n * maximum allowed delay. This prevents synchronized retry spikes and\n * produces a smooth, adaptive backoff pattern.\n *\n * Use this when you need highly desynchronized retries across many\n * clients—especially in distributed systems or high‑traffic services\n * where coordinated retry storms can overwhelm downstream resources.\n *\n * AWS reference:\n * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n *\n * @param {number} baseDelayMs - The minimum delay for any retry attempt.\n * @param {number} maxDelayMs - The maximum allowed delay.\n * @param {number} [prevDelayMs=0] - The delay used for the previous retry.\n * @returns {number} A jittered delay capped at `maxDelayMs`.\n */\nexport function calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs: number = 0): number {\n const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);\n const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);\n return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay)\n}","/**\n * Jitterbug - A lightweight library for building reliable retry behavior\n */\n\nexport { retry, calculateDelay, sleep, type RetryOptions, type BackoffStrategy } from './retry';\n\nimport * as jitterFunctions from './jitter';\nimport { retry } from './retry';\n\nexport default {\n retry,\n ...jitterFunctions\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/jitter.ts","../src/retry.ts","../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,IAAA,cAAA,GAAA,EAAA;AAAA,QAAA,CAAA,cAAA,EAAA;AAAA,EAAA,2BAAA,EAAA,MAAA,2BAAA;AAAA,EAAA,oBAAA,EAAA,MAAA,oBAAA;AAAA,EAAA,oBAAA,EAAA,MAAA,oBAAA;AAAA,EAAA,mBAAA,EAAA,MAAA,mBAAA;AAAA,EAAA,qBAAA,EAAA,MAAA;AAAA,CAAA,CAAA;AAWO,SAAS,mBAAA,CAAoB,YAAoB,UAAA,EAA4B;AAChF,EAAA,OAAO,IAAA,CAAK,MAAA,EAAO,IAAK,UAAA,GAAa,UAAA,CAAA,GAAc,UAAA;AACvD;AAaO,SAAS,qBAAqB,WAAA,EAA6B;AAC9D,EAAA,MAAM,YAAY,WAAA,GAAc,CAAA;AAChC,EAAA,OAAO,SAAA,GAAY,IAAA,CAAK,MAAA,EAAO,GAAI,SAAA;AACvC;AASO,SAAS,oBAAA,CAAqB,YAAoB,YAAA,EAA8B;AACnF,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,UAAA,GAAa,YAAY,CAAA;AAChD;AAiBO,SAAS,qBAAA,CAAsB,aAAqB,cAAA,EAAgC;AAEvF,EAAA,MAAM,iCAAA,GAAA,CAAqC,IAAA,CAAK,MAAA,EAAO,GAAI,IAAI,CAAA,IAAK,cAAA;AACpE,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,WAAA,IAAe,IAAI,iCAAA,CAAkC,CAAA;AAC5E;AAqBO,SAAS,2BAAA,CAA4B,WAAA,EAAqB,UAAA,EAAoB,WAAA,GAAsB,CAAA,EAAW;AAClH,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,cAAc,CAAC,CAAA;AAC1D,EAAA,MAAM,6CAAA,GAAgD,WAAA,GAAc,IAAA,CAAK,MAAA,MAAY,YAAA,GAAe,WAAA,CAAA;AACpG,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,6CAA6C,CAAA;AAC7E;;;AChFA,SAAS,MAAM,EAAA,EAA2B;AACxC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACvD;AAGA,SAAS,cAAA,CAAe,SAAA,EAAmB,OAAA,EAAiB,OAAA,EAAkC;AAC5F,EAAA,QAAQ,OAAA;AAAS,IACf,KAAK,aAAA;AACH,MAAA,OAAO,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,UAAU,CAAC,CAAA;AAAA,IAC5C,KAAK,QAAA;AACH,MAAA,OAAO,SAAA,GAAY,OAAA;AAAA,IACrB,KAAK,OAAA;AAAA,IACL;AACE,MAAA,OAAO,SAAA;AAAA;AAEb;AAUE,SAAS,WAAA,CACP,SAAA,EACA,MAAA,EACA,SAAA,EACQ;AACR,EAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,IAAA,KAAS,QAAQ,OAAO,CAAA;AAE9C,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,OAAA;AACH,MAAA,OAAO,qBAAqB,SAAS,CAAA;AAAA,IAEvC,KAAK,MAAA;AACH,MAAA,OAAO,mBAAA,CAAoB,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,GAAG,CAAA;AAAA,IAEnD,KAAK,OAAA;AACH,MAAA,OAAO,oBAAA,CAAqB,SAAA,EAAW,MAAA,CAAO,MAAM,CAAA;AAAA,IAEtD,KAAK,QAAA;AACH,MAAA,OAAO,qBAAA,CAAsB,SAAA,EAAW,MAAA,CAAO,QAAQ,CAAA;AAAA,IAEzD,KAAK,cAAA;AACH,MAAA,OAAO,2BAAA;AAAA,QACL,SAAA;AAAA,QACA,MAAA,CAAO,QAAA;AAAA,QACP;AAAA,OACF;AAAA;AAEN;AAuBK,SAAS,KAAA,CACd,EAAA,EACA,OAAA,GAAwB,EAAC,EACtB;AACH,EAAA,MAAM;AAAA,IACJ,WAAA,GAAc,CAAA;AAAA,IACd,KAAA,GAAQ,GAAA;AAAA,IACR,OAAA,GAAU,aAAA;AAAA,IACV,YAAA;AAAA,IACA,UAAU,MAAM;AAAA,IAAC;AAAA,GACnB,GAAI,OAAA;AAEJ,EAAA,QAAQ,kBAAkB,IAAA,EAA6C;AACrE,IAAA,IAAI,SAAA;AAEJ,IAAA,IAAI,eAAA,GAAkB,CAAA;AACtB,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,WAAA,EAAa,OAAA,EAAA,EAAW;AACvD,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,MACzB,SAAS,KAAA,EAAO;AACd,QAAA,SAAA,GAAY,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAEpE,QAAA,IAAI,UAAU,WAAA,EAAa;AACzB,UAAA,MAAM,QAAA,GAAW,cAAA,CAAe,KAAA,EAAO,OAAA,EAAS,OAAO,CAAA;AACvD,UAAA,MAAM,QAAA,GAAW,WAAA,CAAY,QAAA,EAAU,YAAA,EAAc,eAAe,CAAA;AACpE,UAAA,eAAA,GAAkB,QAAA;AAClB,UAAA,MAAM,cAAc,QAAA,GAAW,QAAA;AAC/B,UAAA,OAAA,CAAQ,SAAA,EAAW,SAAS,WAAW,CAAA;AACvC,UAAA,MAAM,MAAM,WAAW,CAAA;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,SAAA;AAAA,EACR,CAAA;AACF;;;AC3GA,IAAO,aAAA,GAAQ;AAAA,EACb,KAAA;AAAA,EACA,GAAG;AACL","file":"index.cjs","sourcesContent":["/**\n * Generates a full‑jitter delay within the given range.\n *\n * Full jitter selects a completely random delay between `minDelay` and\n * `maxDelay`, spreading numbers across the entire interval to create jitter.\n *\n * @param {number} minDelayMs - The minimum delay (inclusive).\n * @param {number} maxDelayMs - The maximum delay (exclusive).\n * @returns {number} A random delay between `minDelay` and `maxDelay`.\n * @description Full jitter is ideal when many clients may retry at the same time and you need to aggressively spread those retries out. It’s especially useful under high contention or bursty failure conditions, where a wide range of random delays helps prevent retry storms and reduces load on downstream services.\n */\nexport function calculateFullJitter(minDelayMs: number, maxDelayMs: number): number {\n return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;\n}\n\n/**\n * Calculates an equal‑jitter backoff delay.\n *\n * Equal jitter splits the delay into a fixed half and a random half.\n * This reduces synchronization between clients while avoiding the\n * extremely low delays produced by full jitter.\n *\n * @param {number} baseDelayMs - The original backoff delay before jitter.\n * @returns {number} A delay between half of `baseDelay` and the full `baseDelay`.\n * @description Equal jitter is useful when you want a smoother, more predictable retry pattern that still avoids synchronized retries. It guarantees at least half of the base delay while adding randomness to the other half, giving you a balanced backoff that reduces contention without the extreme variability of full jitter.\n */\nexport function calculateEqualJitter(baseDelayMs: number): number {\n const halfDelay = baseDelayMs / 2;\n return halfDelay + Math.random() * halfDelay;\n}\n\n/**\n * Calculates a fixed‑jitter delay by subtracting a constant amount from the base delay. Unlike random jitter strategies, fixed jitter provides predictable timing while still preventing perfectly aligned retries.\n * Use this when you want a small, consistent desynchronization between clients without introducing randomness — ideal for stable systems where timing consistency matters more than wide jitter distribution.\n * @param {number} maxDelayMs - The original delay before applying jitter.\n * @param {number} jitterAmount - The fixed amount to subtract.\n * @returns {number} The delay after subtracting `jitterAmount`, never below zero.\n */\nexport function calculateFixedJitter(maxDelayMs: number, jitterAmount: number): number {\n return Math.max(0, maxDelayMs - jitterAmount);\n}\n\n/**\n * Calculates a random‑jitter backoff delay by applying a symmetric\n * random variation around the base delay. The jitter fraction determines\n * how far the delay may swing above or below the original value.\n *\n * Random jitter is useful when you want to “wiggle” the delay without\n * dramatically changing its overall shape. It provides light\n * desynchronization between clients while keeping the delay centered\n * around the base value — ideal for systems that need some variability\n * but not the wide spread of full jitter.\n *\n * @param {number} baseDelayMs - The original delay before jitter is applied.\n * @param {number} jitterFraction - Maximum fractional deviation (e.g., 0.2 for ±20%).\n * @returns {number} A jittered delay, clamped to zero if the calculation goes negative.\n */\nexport function calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number {\n // Math.random() gives us 0 to 1 so multiply by 2 and subtract 1 to get -1 to 1 range\n const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;\n return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));\n}\n\n/**\n * Calculates a decorrelated jitter backoff delay using the algorithm\n * popularized by AWS. Each retry delay is chosen randomly between the\n * base delay and three times the previous delay, then capped at the\n * maximum allowed delay. This prevents synchronized retry spikes and\n * produces a smooth, adaptive backoff pattern.\n *\n * Use this when you need highly desynchronized retries across many\n * clients—especially in distributed systems or high‑traffic services\n * where coordinated retry storms can overwhelm downstream resources.\n *\n * AWS reference:\n * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n *\n * @param {number} baseDelayMs - The minimum delay for any retry attempt.\n * @param {number} maxDelayMs - The maximum allowed delay.\n * @param {number} [prevDelayMs=0] - The delay used for the previous retry.\n * @returns {number} A jittered delay capped at `maxDelayMs`.\n */\nexport function calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs: number = 0): number {\n const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);\n const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);\n return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay)\n}","/**\n * Core retry logic implementation\n */\n\nimport { calculateDecorrelatedJitter, calculateEqualJitter, calculateFixedJitter, calculateFullJitter, calculateRandomJitter } from \"./jitter\";\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport type BackoffStrategy = 'exponential' | 'linear' | 'fixed';\nfunction calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number {\n switch (backoff) {\n case 'exponential':\n return baseDelay * Math.pow(2, attempt - 1);\n case 'linear':\n return baseDelay * attempt;\n case 'fixed':\n default:\n return baseDelay;\n }\n}\n\nexport type JitterConfig =\n | { type: 'none' }\n | { type: 'equal' }\n | { type: 'full', min: number, max: number }\n | { type: 'fixed', amount: number }\n | { type: 'random', fraction: number }\n | { type: 'decorrelated', maxDelay: number };\n\n function applyJitter(\n baseDelay: number,\n jitter: JitterConfig | undefined,\n prevDelay: number\n ): number {\n if (!jitter || jitter.type === 'none') return 0;\n \n switch (jitter.type) {\n case 'equal':\n return calculateEqualJitter(baseDelay);\n \n case 'full':\n return calculateFullJitter(jitter.min, jitter.max);\n \n case 'fixed':\n return calculateFixedJitter(baseDelay, jitter.amount);\n \n case 'random':\n return calculateRandomJitter(baseDelay, jitter.fraction);\n \n case 'decorrelated':\n return calculateDecorrelatedJitter(\n baseDelay,\n jitter.maxDelay,\n prevDelay\n );\n }\n }\n \n\n\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxAttempts?: number;\n /** Base delay in milliseconds (default: 1000) */\n delay?: number;\n /** Backoff strategy: 'exponential', 'linear', or 'fixed' (default: 'exponential') */\n backoff?: BackoffStrategy;\n /** Callback called before each retry (error, attempt, waitTime) */\n onRetry?: (error: Error, attempt: number, waitTime: number) => void;\n /** Jitter settings */\n jitterConfig?: JitterConfig\n}\n\n/**\n * @param fn - The async function to retry\n * @param options - Retry configuration\n * @returns A function that wraps the original function with retry logic\n */\n// @typescript-eslint/no-explicit-any\nexport function retry<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n options: RetryOptions = {}\n): T {\n const {\n maxAttempts = 3,\n delay = 1000,\n backoff = 'exponential',\n jitterConfig,\n onRetry = () => {}\n } = options;\n\n return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {\n let lastError: Error;\n \n let prevJitterDelay = 0;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn(...args) as ReturnType<T>;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n \n if (attempt < maxAttempts) {\n const waitTime = calculateDelay(delay, attempt, backoff);\n const jitterMs = applyJitter(waitTime, jitterConfig, prevJitterDelay)\n prevJitterDelay = jitterMs;\n const delayTimeMs = waitTime + jitterMs;\n onRetry(lastError, attempt, delayTimeMs);\n await sleep(delayTimeMs);\n }\n }\n }\n \n throw lastError!;\n }) as T;\n}\n\nexport { calculateDelay, sleep, applyJitter };\n\n","/**\n * Jitterbug - A lightweight library for building reliable retry behavior\n */\n\nexport { retry, calculateDelay, sleep, type RetryOptions, type BackoffStrategy } from './retry';\n\nimport * as jitterFunctions from './jitter';\nimport { retry } from './retry';\n\nexport default {\n retry,\n ...jitterFunctions\n};\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,8 +2,26 @@
|
|
|
2
2
|
* Core retry logic implementation
|
|
3
3
|
*/
|
|
4
4
|
declare function sleep(ms: number): Promise<void>;
|
|
5
|
-
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
6
5
|
type BackoffStrategy = 'exponential' | 'linear' | 'fixed';
|
|
6
|
+
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
7
|
+
type JitterConfig = {
|
|
8
|
+
type: 'none';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'equal';
|
|
11
|
+
} | {
|
|
12
|
+
type: 'full';
|
|
13
|
+
min: number;
|
|
14
|
+
max: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'fixed';
|
|
17
|
+
amount: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'random';
|
|
20
|
+
fraction: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'decorrelated';
|
|
23
|
+
maxDelay: number;
|
|
24
|
+
};
|
|
7
25
|
interface RetryOptions {
|
|
8
26
|
/** Maximum number of retry attempts (default: 3) */
|
|
9
27
|
maxAttempts?: number;
|
|
@@ -13,9 +31,10 @@ interface RetryOptions {
|
|
|
13
31
|
backoff?: BackoffStrategy;
|
|
14
32
|
/** Callback called before each retry (error, attempt, waitTime) */
|
|
15
33
|
onRetry?: (error: Error, attempt: number, waitTime: number) => void;
|
|
34
|
+
/** Jitter settings */
|
|
35
|
+
jitterConfig?: JitterConfig;
|
|
16
36
|
}
|
|
17
37
|
/**
|
|
18
|
-
* Creates a retry wrapper function
|
|
19
38
|
* @param fn - The async function to retry
|
|
20
39
|
* @param options - Retry configuration
|
|
21
40
|
* @returns A function that wraps the original function with retry logic
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,26 @@
|
|
|
2
2
|
* Core retry logic implementation
|
|
3
3
|
*/
|
|
4
4
|
declare function sleep(ms: number): Promise<void>;
|
|
5
|
-
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
6
5
|
type BackoffStrategy = 'exponential' | 'linear' | 'fixed';
|
|
6
|
+
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
7
|
+
type JitterConfig = {
|
|
8
|
+
type: 'none';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'equal';
|
|
11
|
+
} | {
|
|
12
|
+
type: 'full';
|
|
13
|
+
min: number;
|
|
14
|
+
max: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'fixed';
|
|
17
|
+
amount: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'random';
|
|
20
|
+
fraction: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'decorrelated';
|
|
23
|
+
maxDelay: number;
|
|
24
|
+
};
|
|
7
25
|
interface RetryOptions {
|
|
8
26
|
/** Maximum number of retry attempts (default: 3) */
|
|
9
27
|
maxAttempts?: number;
|
|
@@ -13,9 +31,10 @@ interface RetryOptions {
|
|
|
13
31
|
backoff?: BackoffStrategy;
|
|
14
32
|
/** Callback called before each retry (error, attempt, waitTime) */
|
|
15
33
|
onRetry?: (error: Error, attempt: number, waitTime: number) => void;
|
|
34
|
+
/** Jitter settings */
|
|
35
|
+
jitterConfig?: JitterConfig;
|
|
16
36
|
}
|
|
17
37
|
/**
|
|
18
|
-
* Creates a retry wrapper function
|
|
19
38
|
* @param fn - The async function to retry
|
|
20
39
|
* @param options - Retry configuration
|
|
21
40
|
* @returns A function that wraps the original function with retry logic
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,35 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/jitter.ts
|
|
8
|
+
var jitter_exports = {};
|
|
9
|
+
__export(jitter_exports, {
|
|
10
|
+
calculateDecorrelatedJitter: () => calculateDecorrelatedJitter,
|
|
11
|
+
calculateEqualJitter: () => calculateEqualJitter,
|
|
12
|
+
calculateFixedJitter: () => calculateFixedJitter,
|
|
13
|
+
calculateFullJitter: () => calculateFullJitter,
|
|
14
|
+
calculateRandomJitter: () => calculateRandomJitter
|
|
15
|
+
});
|
|
16
|
+
function calculateFullJitter(minDelayMs, maxDelayMs) {
|
|
17
|
+
return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;
|
|
18
|
+
}
|
|
19
|
+
function calculateEqualJitter(baseDelayMs) {
|
|
20
|
+
const halfDelay = baseDelayMs / 2;
|
|
21
|
+
return halfDelay + Math.random() * halfDelay;
|
|
22
|
+
}
|
|
23
|
+
function calculateFixedJitter(maxDelayMs, jitterAmount) {
|
|
24
|
+
return Math.max(0, maxDelayMs - jitterAmount);
|
|
25
|
+
}
|
|
26
|
+
function calculateRandomJitter(baseDelayMs, jitterFraction) {
|
|
27
|
+
const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;
|
|
28
|
+
return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));
|
|
29
|
+
}
|
|
30
|
+
function calculateDecorrelatedJitter(baseDelayMs, maxDelayMs, prevDelayMs = 0) {
|
|
31
|
+
const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);
|
|
32
|
+
const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);
|
|
33
|
+
return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay);
|
|
34
|
+
}
|
|
35
|
+
|
|
7
36
|
// src/retry.ts
|
|
8
37
|
function sleep(ms) {
|
|
9
38
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -19,16 +48,37 @@ function calculateDelay(baseDelay, attempt, backoff) {
|
|
|
19
48
|
return baseDelay;
|
|
20
49
|
}
|
|
21
50
|
}
|
|
51
|
+
function applyJitter(baseDelay, jitter, prevDelay) {
|
|
52
|
+
if (!jitter || jitter.type === "none") return 0;
|
|
53
|
+
switch (jitter.type) {
|
|
54
|
+
case "equal":
|
|
55
|
+
return calculateEqualJitter(baseDelay);
|
|
56
|
+
case "full":
|
|
57
|
+
return calculateFullJitter(jitter.min, jitter.max);
|
|
58
|
+
case "fixed":
|
|
59
|
+
return calculateFixedJitter(baseDelay, jitter.amount);
|
|
60
|
+
case "random":
|
|
61
|
+
return calculateRandomJitter(baseDelay, jitter.fraction);
|
|
62
|
+
case "decorrelated":
|
|
63
|
+
return calculateDecorrelatedJitter(
|
|
64
|
+
baseDelay,
|
|
65
|
+
jitter.maxDelay,
|
|
66
|
+
prevDelay
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
22
70
|
function retry(fn, options = {}) {
|
|
23
71
|
const {
|
|
24
72
|
maxAttempts = 3,
|
|
25
73
|
delay = 1e3,
|
|
26
74
|
backoff = "exponential",
|
|
75
|
+
jitterConfig,
|
|
27
76
|
onRetry = () => {
|
|
28
77
|
}
|
|
29
78
|
} = options;
|
|
30
79
|
return (async function(...args) {
|
|
31
80
|
let lastError;
|
|
81
|
+
let prevJitterDelay = 0;
|
|
32
82
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
33
83
|
try {
|
|
34
84
|
return await fn(...args);
|
|
@@ -36,8 +86,11 @@ function retry(fn, options = {}) {
|
|
|
36
86
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
37
87
|
if (attempt < maxAttempts) {
|
|
38
88
|
const waitTime = calculateDelay(delay, attempt, backoff);
|
|
39
|
-
|
|
40
|
-
|
|
89
|
+
const jitterMs = applyJitter(waitTime, jitterConfig, prevJitterDelay);
|
|
90
|
+
prevJitterDelay = jitterMs;
|
|
91
|
+
const delayTimeMs = waitTime + jitterMs;
|
|
92
|
+
onRetry(lastError, attempt, delayTimeMs);
|
|
93
|
+
await sleep(delayTimeMs);
|
|
41
94
|
}
|
|
42
95
|
}
|
|
43
96
|
}
|
|
@@ -45,35 +98,6 @@ function retry(fn, options = {}) {
|
|
|
45
98
|
});
|
|
46
99
|
}
|
|
47
100
|
|
|
48
|
-
// src/jitter.ts
|
|
49
|
-
var jitter_exports = {};
|
|
50
|
-
__export(jitter_exports, {
|
|
51
|
-
calculateDecorrelatedJitter: () => calculateDecorrelatedJitter,
|
|
52
|
-
calculateEqualJitter: () => calculateEqualJitter,
|
|
53
|
-
calculateFixedJitter: () => calculateFixedJitter,
|
|
54
|
-
calculateFullJitter: () => calculateFullJitter,
|
|
55
|
-
calculateRandomJitter: () => calculateRandomJitter
|
|
56
|
-
});
|
|
57
|
-
function calculateFullJitter(minDelayMs, maxDelayMs) {
|
|
58
|
-
return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;
|
|
59
|
-
}
|
|
60
|
-
function calculateEqualJitter(baseDelayMs) {
|
|
61
|
-
const halfDelay = baseDelayMs / 2;
|
|
62
|
-
return halfDelay + Math.random() * halfDelay;
|
|
63
|
-
}
|
|
64
|
-
function calculateFixedJitter(maxDelayMs, jitterAmount) {
|
|
65
|
-
return Math.max(0, maxDelayMs - jitterAmount);
|
|
66
|
-
}
|
|
67
|
-
function calculateRandomJitter(baseDelayMs, jitterFraction) {
|
|
68
|
-
const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;
|
|
69
|
-
return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));
|
|
70
|
-
}
|
|
71
|
-
function calculateDecorrelatedJitter(baseDelayMs, maxDelayMs, prevDelayMs = 0) {
|
|
72
|
-
const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);
|
|
73
|
-
const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);
|
|
74
|
-
return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
101
|
// src/index.ts
|
|
78
102
|
var index_default = {
|
|
79
103
|
retry,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/jitter.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Core retry logic implementation\n */\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number {\n switch (backoff) {\n case 'exponential':\n return baseDelay * Math.pow(2, attempt - 1);\n case 'linear':\n return baseDelay * attempt;\n case 'fixed':\n default:\n return baseDelay;\n }\n}\n\nexport type BackoffStrategy = 'exponential' | 'linear' | 'fixed';\n\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxAttempts?: number;\n /** Base delay in milliseconds (default: 1000) */\n delay?: number;\n /** Backoff strategy: 'exponential', 'linear', or 'fixed' (default: 'exponential') */\n backoff?: BackoffStrategy;\n /** Callback called before each retry (error, attempt, waitTime) */\n onRetry?: (error: Error, attempt: number, waitTime: number) => void;\n}\n\n/**\n * Creates a retry wrapper function\n * @param fn - The async function to retry\n * @param options - Retry configuration\n * @returns A function that wraps the original function with retry logic\n */\n// @typescript-eslint/no-explicit-any\nexport function retry<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n options: RetryOptions = {}\n): T {\n const {\n maxAttempts = 3,\n delay = 1000,\n backoff = 'exponential',\n onRetry = () => {}\n } = options;\n\n return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {\n let lastError: Error;\n \n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn(...args) as ReturnType<T>;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n \n if (attempt < maxAttempts) {\n const waitTime = calculateDelay(delay, attempt, backoff);\n onRetry(lastError, attempt, waitTime);\n await sleep(waitTime);\n }\n }\n }\n \n throw lastError!;\n }) as T;\n}\n\nexport { calculateDelay, sleep };\n\n","/**\n * Generates a full‑jitter delay within the given range.\n *\n * Full jitter selects a completely random delay between `minDelay` and\n * `maxDelay`, spreading numbers across the entire interval to create jitter.\n *\n * @param {number} minDelayMs - The minimum delay (inclusive).\n * @param {number} maxDelayMs - The maximum delay (exclusive).\n * @returns {number} A random delay between `minDelay` and `maxDelay`.\n * @description Full jitter is ideal when many clients may retry at the same time and you need to aggressively spread those retries out. It’s especially useful under high contention or bursty failure conditions, where a wide range of random delays helps prevent retry storms and reduces load on downstream services.\n */\nexport function calculateFullJitter(minDelayMs: number, maxDelayMs: number): number {\n return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;\n}\n\n/**\n * Calculates an equal‑jitter backoff delay.\n *\n * Equal jitter splits the delay into a fixed half and a random half.\n * This reduces synchronization between clients while avoiding the\n * extremely low delays produced by full jitter.\n *\n * @param {number} baseDelayMs - The original backoff delay before jitter.\n * @returns {number} A delay between half of `baseDelay` and the full `baseDelay`.\n * @description Equal jitter is useful when you want a smoother, more predictable retry pattern that still avoids synchronized retries. It guarantees at least half of the base delay while adding randomness to the other half, giving you a balanced backoff that reduces contention without the extreme variability of full jitter.\n */\nexport function calculateEqualJitter(baseDelayMs: number): number {\n const halfDelay = baseDelayMs / 2;\n return halfDelay + Math.random() * halfDelay;\n}\n\n\n/**\n * Calculates a fixed‑jitter delay by subtracting a constant amount from the base delay. Unlike random jitter strategies, fixed jitter provides predictable timing while still preventing perfectly aligned retries.\n * Use this when you want a small, consistent desynchronization between clients without introducing randomness — ideal for stable systems where timing consistency matters more than wide jitter distribution.\n * @param {number} maxDelayMs - The original delay before applying jitter.\n * @param {number} jitterAmount - The fixed amount to subtract.\n * @returns {number} The delay after subtracting `jitterAmount`, never below zero.\n */\nexport function calculateFixedJitter(maxDelayMs: number, jitterAmount: number): number {\n return Math.max(0, maxDelayMs - jitterAmount);\n}\n\n/**\n * Calculates a random‑jitter backoff delay by applying a symmetric\n * random variation around the base delay. The jitter fraction determines\n * how far the delay may swing above or below the original value.\n *\n * Random jitter is useful when you want to “wiggle” the delay without\n * dramatically changing its overall shape. It provides light\n * desynchronization between clients while keeping the delay centered\n * around the base value — ideal for systems that need some variability\n * but not the wide spread of full jitter.\n *\n * @param {number} baseDelayMs - The original delay before jitter is applied.\n * @param {number} jitterFraction - Maximum fractional deviation (e.g., 0.2 for ±20%).\n * @returns {number} A jittered delay, clamped to zero if the calculation goes negative.\n */\nexport function calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number {\n // Math.random() gives us 0 to 1 so multiply by 2 and subtract 1 to get -1 to 1 range\n const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;\n return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));\n}\n\n/**\n * Calculates a decorrelated jitter backoff delay using the algorithm\n * popularized by AWS. Each retry delay is chosen randomly between the\n * base delay and three times the previous delay, then capped at the\n * maximum allowed delay. This prevents synchronized retry spikes and\n * produces a smooth, adaptive backoff pattern.\n *\n * Use this when you need highly desynchronized retries across many\n * clients—especially in distributed systems or high‑traffic services\n * where coordinated retry storms can overwhelm downstream resources.\n *\n * AWS reference:\n * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n *\n * @param {number} baseDelayMs - The minimum delay for any retry attempt.\n * @param {number} maxDelayMs - The maximum allowed delay.\n * @param {number} [prevDelayMs=0] - The delay used for the previous retry.\n * @returns {number} A jittered delay capped at `maxDelayMs`.\n */\nexport function calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs: number = 0): number {\n const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);\n const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);\n return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay)\n}","/**\n * Jitterbug - A lightweight library for building reliable retry behavior\n */\n\nexport { retry, calculateDelay, sleep, type RetryOptions, type BackoffStrategy } from './retry';\n\nimport * as jitterFunctions from './jitter';\nimport { retry } from './retry';\n\nexport default {\n retry,\n ...jitterFunctions\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/jitter.ts","../src/retry.ts","../src/index.ts"],"names":[],"mappings":";;;;;;;AAAA,IAAA,cAAA,GAAA,EAAA;AAAA,QAAA,CAAA,cAAA,EAAA;AAAA,EAAA,2BAAA,EAAA,MAAA,2BAAA;AAAA,EAAA,oBAAA,EAAA,MAAA,oBAAA;AAAA,EAAA,oBAAA,EAAA,MAAA,oBAAA;AAAA,EAAA,mBAAA,EAAA,MAAA,mBAAA;AAAA,EAAA,qBAAA,EAAA,MAAA;AAAA,CAAA,CAAA;AAWO,SAAS,mBAAA,CAAoB,YAAoB,UAAA,EAA4B;AAChF,EAAA,OAAO,IAAA,CAAK,MAAA,EAAO,IAAK,UAAA,GAAa,UAAA,CAAA,GAAc,UAAA;AACvD;AAaO,SAAS,qBAAqB,WAAA,EAA6B;AAC9D,EAAA,MAAM,YAAY,WAAA,GAAc,CAAA;AAChC,EAAA,OAAO,SAAA,GAAY,IAAA,CAAK,MAAA,EAAO,GAAI,SAAA;AACvC;AASO,SAAS,oBAAA,CAAqB,YAAoB,YAAA,EAA8B;AACnF,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,UAAA,GAAa,YAAY,CAAA;AAChD;AAiBO,SAAS,qBAAA,CAAsB,aAAqB,cAAA,EAAgC;AAEvF,EAAA,MAAM,iCAAA,GAAA,CAAqC,IAAA,CAAK,MAAA,EAAO,GAAI,IAAI,CAAA,IAAK,cAAA;AACpE,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,WAAA,IAAe,IAAI,iCAAA,CAAkC,CAAA;AAC5E;AAqBO,SAAS,2BAAA,CAA4B,WAAA,EAAqB,UAAA,EAAoB,WAAA,GAAsB,CAAA,EAAW;AAClH,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,cAAc,CAAC,CAAA;AAC1D,EAAA,MAAM,6CAAA,GAAgD,WAAA,GAAc,IAAA,CAAK,MAAA,MAAY,YAAA,GAAe,WAAA,CAAA;AACpG,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,6CAA6C,CAAA;AAC7E;;;AChFA,SAAS,MAAM,EAAA,EAA2B;AACxC,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AACvD;AAGA,SAAS,cAAA,CAAe,SAAA,EAAmB,OAAA,EAAiB,OAAA,EAAkC;AAC5F,EAAA,QAAQ,OAAA;AAAS,IACf,KAAK,aAAA;AACH,MAAA,OAAO,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,UAAU,CAAC,CAAA;AAAA,IAC5C,KAAK,QAAA;AACH,MAAA,OAAO,SAAA,GAAY,OAAA;AAAA,IACrB,KAAK,OAAA;AAAA,IACL;AACE,MAAA,OAAO,SAAA;AAAA;AAEb;AAUE,SAAS,WAAA,CACP,SAAA,EACA,MAAA,EACA,SAAA,EACQ;AACR,EAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,IAAA,KAAS,QAAQ,OAAO,CAAA;AAE9C,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,OAAA;AACH,MAAA,OAAO,qBAAqB,SAAS,CAAA;AAAA,IAEvC,KAAK,MAAA;AACH,MAAA,OAAO,mBAAA,CAAoB,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,GAAG,CAAA;AAAA,IAEnD,KAAK,OAAA;AACH,MAAA,OAAO,oBAAA,CAAqB,SAAA,EAAW,MAAA,CAAO,MAAM,CAAA;AAAA,IAEtD,KAAK,QAAA;AACH,MAAA,OAAO,qBAAA,CAAsB,SAAA,EAAW,MAAA,CAAO,QAAQ,CAAA;AAAA,IAEzD,KAAK,cAAA;AACH,MAAA,OAAO,2BAAA;AAAA,QACL,SAAA;AAAA,QACA,MAAA,CAAO,QAAA;AAAA,QACP;AAAA,OACF;AAAA;AAEN;AAuBK,SAAS,KAAA,CACd,EAAA,EACA,OAAA,GAAwB,EAAC,EACtB;AACH,EAAA,MAAM;AAAA,IACJ,WAAA,GAAc,CAAA;AAAA,IACd,KAAA,GAAQ,GAAA;AAAA,IACR,OAAA,GAAU,aAAA;AAAA,IACV,YAAA;AAAA,IACA,UAAU,MAAM;AAAA,IAAC;AAAA,GACnB,GAAI,OAAA;AAEJ,EAAA,QAAQ,kBAAkB,IAAA,EAA6C;AACrE,IAAA,IAAI,SAAA;AAEJ,IAAA,IAAI,eAAA,GAAkB,CAAA;AACtB,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,WAAA,EAAa,OAAA,EAAA,EAAW;AACvD,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,EAAA,CAAG,GAAG,IAAI,CAAA;AAAA,MACzB,SAAS,KAAA,EAAO;AACd,QAAA,SAAA,GAAY,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAEpE,QAAA,IAAI,UAAU,WAAA,EAAa;AACzB,UAAA,MAAM,QAAA,GAAW,cAAA,CAAe,KAAA,EAAO,OAAA,EAAS,OAAO,CAAA;AACvD,UAAA,MAAM,QAAA,GAAW,WAAA,CAAY,QAAA,EAAU,YAAA,EAAc,eAAe,CAAA;AACpE,UAAA,eAAA,GAAkB,QAAA;AAClB,UAAA,MAAM,cAAc,QAAA,GAAW,QAAA;AAC/B,UAAA,OAAA,CAAQ,SAAA,EAAW,SAAS,WAAW,CAAA;AACvC,UAAA,MAAM,MAAM,WAAW,CAAA;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,SAAA;AAAA,EACR,CAAA;AACF;;;AC3GA,IAAO,aAAA,GAAQ;AAAA,EACb,KAAA;AAAA,EACA,GAAG;AACL","file":"index.js","sourcesContent":["/**\n * Generates a full‑jitter delay within the given range.\n *\n * Full jitter selects a completely random delay between `minDelay` and\n * `maxDelay`, spreading numbers across the entire interval to create jitter.\n *\n * @param {number} minDelayMs - The minimum delay (inclusive).\n * @param {number} maxDelayMs - The maximum delay (exclusive).\n * @returns {number} A random delay between `minDelay` and `maxDelay`.\n * @description Full jitter is ideal when many clients may retry at the same time and you need to aggressively spread those retries out. It’s especially useful under high contention or bursty failure conditions, where a wide range of random delays helps prevent retry storms and reduces load on downstream services.\n */\nexport function calculateFullJitter(minDelayMs: number, maxDelayMs: number): number {\n return Math.random() * (maxDelayMs - minDelayMs) + minDelayMs;\n}\n\n/**\n * Calculates an equal‑jitter backoff delay.\n *\n * Equal jitter splits the delay into a fixed half and a random half.\n * This reduces synchronization between clients while avoiding the\n * extremely low delays produced by full jitter.\n *\n * @param {number} baseDelayMs - The original backoff delay before jitter.\n * @returns {number} A delay between half of `baseDelay` and the full `baseDelay`.\n * @description Equal jitter is useful when you want a smoother, more predictable retry pattern that still avoids synchronized retries. It guarantees at least half of the base delay while adding randomness to the other half, giving you a balanced backoff that reduces contention without the extreme variability of full jitter.\n */\nexport function calculateEqualJitter(baseDelayMs: number): number {\n const halfDelay = baseDelayMs / 2;\n return halfDelay + Math.random() * halfDelay;\n}\n\n/**\n * Calculates a fixed‑jitter delay by subtracting a constant amount from the base delay. Unlike random jitter strategies, fixed jitter provides predictable timing while still preventing perfectly aligned retries.\n * Use this when you want a small, consistent desynchronization between clients without introducing randomness — ideal for stable systems where timing consistency matters more than wide jitter distribution.\n * @param {number} maxDelayMs - The original delay before applying jitter.\n * @param {number} jitterAmount - The fixed amount to subtract.\n * @returns {number} The delay after subtracting `jitterAmount`, never below zero.\n */\nexport function calculateFixedJitter(maxDelayMs: number, jitterAmount: number): number {\n return Math.max(0, maxDelayMs - jitterAmount);\n}\n\n/**\n * Calculates a random‑jitter backoff delay by applying a symmetric\n * random variation around the base delay. The jitter fraction determines\n * how far the delay may swing above or below the original value.\n *\n * Random jitter is useful when you want to “wiggle” the delay without\n * dramatically changing its overall shape. It provides light\n * desynchronization between clients while keeping the delay centered\n * around the base value — ideal for systems that need some variability\n * but not the wide spread of full jitter.\n *\n * @param {number} baseDelayMs - The original delay before jitter is applied.\n * @param {number} jitterFraction - Maximum fractional deviation (e.g., 0.2 for ±20%).\n * @returns {number} A jittered delay, clamped to zero if the calculation goes negative.\n */\nexport function calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number {\n // Math.random() gives us 0 to 1 so multiply by 2 and subtract 1 to get -1 to 1 range\n const randomNumberBetweenJitterFraction = (Math.random() * 2 - 1) * jitterFraction;\n return Math.max(0, baseDelayMs * (1 + randomNumberBetweenJitterFraction));\n}\n\n/**\n * Calculates a decorrelated jitter backoff delay using the algorithm\n * popularized by AWS. Each retry delay is chosen randomly between the\n * base delay and three times the previous delay, then capped at the\n * maximum allowed delay. This prevents synchronized retry spikes and\n * produces a smooth, adaptive backoff pattern.\n *\n * Use this when you need highly desynchronized retries across many\n * clients—especially in distributed systems or high‑traffic services\n * where coordinated retry storms can overwhelm downstream resources.\n *\n * AWS reference:\n * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n *\n * @param {number} baseDelayMs - The minimum delay for any retry attempt.\n * @param {number} maxDelayMs - The maximum allowed delay.\n * @param {number} [prevDelayMs=0] - The delay used for the previous retry.\n * @returns {number} A jittered delay capped at `maxDelayMs`.\n */\nexport function calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs: number = 0): number {\n const upperDelayMs = Math.max(baseDelayMs, prevDelayMs * 3);\n const randomValueBetweenBaseDelayAnd3xPrevoiusDelay = baseDelayMs + Math.random() * (upperDelayMs - baseDelayMs);\n return Math.min(maxDelayMs, randomValueBetweenBaseDelayAnd3xPrevoiusDelay)\n}","/**\n * Core retry logic implementation\n */\n\nimport { calculateDecorrelatedJitter, calculateEqualJitter, calculateFixedJitter, calculateFullJitter, calculateRandomJitter } from \"./jitter\";\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport type BackoffStrategy = 'exponential' | 'linear' | 'fixed';\nfunction calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number {\n switch (backoff) {\n case 'exponential':\n return baseDelay * Math.pow(2, attempt - 1);\n case 'linear':\n return baseDelay * attempt;\n case 'fixed':\n default:\n return baseDelay;\n }\n}\n\nexport type JitterConfig =\n | { type: 'none' }\n | { type: 'equal' }\n | { type: 'full', min: number, max: number }\n | { type: 'fixed', amount: number }\n | { type: 'random', fraction: number }\n | { type: 'decorrelated', maxDelay: number };\n\n function applyJitter(\n baseDelay: number,\n jitter: JitterConfig | undefined,\n prevDelay: number\n ): number {\n if (!jitter || jitter.type === 'none') return 0;\n \n switch (jitter.type) {\n case 'equal':\n return calculateEqualJitter(baseDelay);\n \n case 'full':\n return calculateFullJitter(jitter.min, jitter.max);\n \n case 'fixed':\n return calculateFixedJitter(baseDelay, jitter.amount);\n \n case 'random':\n return calculateRandomJitter(baseDelay, jitter.fraction);\n \n case 'decorrelated':\n return calculateDecorrelatedJitter(\n baseDelay,\n jitter.maxDelay,\n prevDelay\n );\n }\n }\n \n\n\nexport interface RetryOptions {\n /** Maximum number of retry attempts (default: 3) */\n maxAttempts?: number;\n /** Base delay in milliseconds (default: 1000) */\n delay?: number;\n /** Backoff strategy: 'exponential', 'linear', or 'fixed' (default: 'exponential') */\n backoff?: BackoffStrategy;\n /** Callback called before each retry (error, attempt, waitTime) */\n onRetry?: (error: Error, attempt: number, waitTime: number) => void;\n /** Jitter settings */\n jitterConfig?: JitterConfig\n}\n\n/**\n * @param fn - The async function to retry\n * @param options - Retry configuration\n * @returns A function that wraps the original function with retry logic\n */\n// @typescript-eslint/no-explicit-any\nexport function retry<T extends (...args: unknown[]) => Promise<unknown>>(\n fn: T,\n options: RetryOptions = {}\n): T {\n const {\n maxAttempts = 3,\n delay = 1000,\n backoff = 'exponential',\n jitterConfig,\n onRetry = () => {}\n } = options;\n\n return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {\n let lastError: Error;\n \n let prevJitterDelay = 0;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn(...args) as ReturnType<T>;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n \n if (attempt < maxAttempts) {\n const waitTime = calculateDelay(delay, attempt, backoff);\n const jitterMs = applyJitter(waitTime, jitterConfig, prevJitterDelay)\n prevJitterDelay = jitterMs;\n const delayTimeMs = waitTime + jitterMs;\n onRetry(lastError, attempt, delayTimeMs);\n await sleep(delayTimeMs);\n }\n }\n }\n \n throw lastError!;\n }) as T;\n}\n\nexport { calculateDelay, sleep, applyJitter };\n\n","/**\n * Jitterbug - A lightweight library for building reliable retry behavior\n */\n\nexport { retry, calculateDelay, sleep, type RetryOptions, type BackoffStrategy } from './retry';\n\nimport * as jitterFunctions from './jitter';\nimport { retry } from './retry';\n\nexport default {\n retry,\n ...jitterFunctions\n};\n"]}
|
package/package.json
CHANGED