jitterbug 1.0.0-alpha.2 → 1.0.0-alpha.4
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 +5 -5
- package/dist/index.cjs +40 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -161,14 +161,14 @@ jitterbug/
|
|
|
161
161
|
|
|
162
162
|
## Test Coverage
|
|
163
163
|
|
|
164
|
-

|
|
165
165
|
|
|
166
166
|
| Metric | Coverage |
|
|
167
167
|
|--------|----------|
|
|
168
|
-
| Statements |
|
|
169
|
-
| Branches |
|
|
170
|
-
| Functions |
|
|
171
|
-
| Lines |
|
|
168
|
+
| Statements | 100.00% |
|
|
169
|
+
| Branches | 95.00% |
|
|
170
|
+
| Functions | 100.00% |
|
|
171
|
+
| Lines | 100.00% |
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
5
11
|
// src/retry.ts
|
|
12
|
+
function sleep(ms) {
|
|
13
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
+
}
|
|
6
15
|
function calculateDelay(baseDelay, attempt, backoff) {
|
|
7
16
|
switch (backoff) {
|
|
8
17
|
case "exponential":
|
|
@@ -14,9 +23,6 @@ function calculateDelay(baseDelay, attempt, backoff) {
|
|
|
14
23
|
return baseDelay;
|
|
15
24
|
}
|
|
16
25
|
}
|
|
17
|
-
function sleep(ms) {
|
|
18
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
|
-
}
|
|
20
26
|
function retry(fn, options = {}) {
|
|
21
27
|
const {
|
|
22
28
|
maxAttempts = 3,
|
|
@@ -43,9 +49,39 @@ function retry(fn, options = {}) {
|
|
|
43
49
|
});
|
|
44
50
|
}
|
|
45
51
|
|
|
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
|
+
|
|
46
81
|
// src/index.ts
|
|
47
82
|
var index_default = {
|
|
48
|
-
retry
|
|
83
|
+
retry,
|
|
84
|
+
...jitter_exports
|
|
49
85
|
};
|
|
50
86
|
|
|
51
87
|
exports.calculateDelay = calculateDelay;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/index.ts"],"names":[],"mappings":"
|
|
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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core retry logic implementation
|
|
3
3
|
*/
|
|
4
|
+
declare function sleep(ms: number): Promise<void>;
|
|
5
|
+
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
4
6
|
type BackoffStrategy = 'exponential' | 'linear' | 'fixed';
|
|
5
7
|
interface RetryOptions {
|
|
6
8
|
/** Maximum number of retry attempts (default: 3) */
|
|
@@ -12,21 +14,24 @@ interface RetryOptions {
|
|
|
12
14
|
/** Callback called before each retry (error, attempt, waitTime) */
|
|
13
15
|
onRetry?: (error: Error, attempt: number, waitTime: number) => void;
|
|
14
16
|
}
|
|
15
|
-
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
16
|
-
declare function sleep(ms: number): Promise<void>;
|
|
17
17
|
/**
|
|
18
18
|
* Creates a retry wrapper function
|
|
19
19
|
* @param fn - The async function to retry
|
|
20
20
|
* @param options - Retry configuration
|
|
21
21
|
* @returns A function that wraps the original function with retry logic
|
|
22
22
|
*/
|
|
23
|
-
declare function retry<T extends (...args:
|
|
23
|
+
declare function retry<T extends (...args: unknown[]) => Promise<unknown>>(fn: T, options?: RetryOptions): T;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Jitterbug - A lightweight library for building reliable retry behavior
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
declare const _default: {
|
|
30
|
+
calculateFullJitter(minDelayMs: number, maxDelayMs: number): number;
|
|
31
|
+
calculateEqualJitter(baseDelayMs: number): number;
|
|
32
|
+
calculateFixedJitter(maxDelayMs: number, jitterAmount: number): number;
|
|
33
|
+
calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number;
|
|
34
|
+
calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs?: number): number;
|
|
30
35
|
retry: typeof retry;
|
|
31
36
|
};
|
|
32
37
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core retry logic implementation
|
|
3
3
|
*/
|
|
4
|
+
declare function sleep(ms: number): Promise<void>;
|
|
5
|
+
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
4
6
|
type BackoffStrategy = 'exponential' | 'linear' | 'fixed';
|
|
5
7
|
interface RetryOptions {
|
|
6
8
|
/** Maximum number of retry attempts (default: 3) */
|
|
@@ -12,21 +14,24 @@ interface RetryOptions {
|
|
|
12
14
|
/** Callback called before each retry (error, attempt, waitTime) */
|
|
13
15
|
onRetry?: (error: Error, attempt: number, waitTime: number) => void;
|
|
14
16
|
}
|
|
15
|
-
declare function calculateDelay(baseDelay: number, attempt: number, backoff: BackoffStrategy): number;
|
|
16
|
-
declare function sleep(ms: number): Promise<void>;
|
|
17
17
|
/**
|
|
18
18
|
* Creates a retry wrapper function
|
|
19
19
|
* @param fn - The async function to retry
|
|
20
20
|
* @param options - Retry configuration
|
|
21
21
|
* @returns A function that wraps the original function with retry logic
|
|
22
22
|
*/
|
|
23
|
-
declare function retry<T extends (...args:
|
|
23
|
+
declare function retry<T extends (...args: unknown[]) => Promise<unknown>>(fn: T, options?: RetryOptions): T;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Jitterbug - A lightweight library for building reliable retry behavior
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
declare const _default: {
|
|
30
|
+
calculateFullJitter(minDelayMs: number, maxDelayMs: number): number;
|
|
31
|
+
calculateEqualJitter(baseDelayMs: number): number;
|
|
32
|
+
calculateFixedJitter(maxDelayMs: number, jitterAmount: number): number;
|
|
33
|
+
calculateRandomJitter(baseDelayMs: number, jitterFraction: number): number;
|
|
34
|
+
calculateDecorrelatedJitter(baseDelayMs: number, maxDelayMs: number, prevDelayMs?: number): number;
|
|
30
35
|
retry: typeof retry;
|
|
31
36
|
};
|
|
32
37
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
// src/retry.ts
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
2
11
|
function calculateDelay(baseDelay, attempt, backoff) {
|
|
3
12
|
switch (backoff) {
|
|
4
13
|
case "exponential":
|
|
@@ -10,9 +19,6 @@ function calculateDelay(baseDelay, attempt, backoff) {
|
|
|
10
19
|
return baseDelay;
|
|
11
20
|
}
|
|
12
21
|
}
|
|
13
|
-
function sleep(ms) {
|
|
14
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
-
}
|
|
16
22
|
function retry(fn, options = {}) {
|
|
17
23
|
const {
|
|
18
24
|
maxAttempts = 3,
|
|
@@ -39,9 +45,39 @@ function retry(fn, options = {}) {
|
|
|
39
45
|
});
|
|
40
46
|
}
|
|
41
47
|
|
|
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
|
+
|
|
42
77
|
// src/index.ts
|
|
43
78
|
var index_default = {
|
|
44
|
-
retry
|
|
79
|
+
retry,
|
|
80
|
+
...jitter_exports
|
|
45
81
|
};
|
|
46
82
|
|
|
47
83
|
export { calculateDelay, index_default as default, retry, sleep };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/index.ts"],"names":[],"mappings":";
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jitterbug",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
4
|
"description": "A lightweight library for building reliable retry behavior in distributed systems and API clients",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsup",
|
|
21
21
|
"dev": "tsup --watch",
|
|
22
|
+
"lint": "eslint . --ext .ts,.js",
|
|
23
|
+
"lint:fix": "eslint . --ext .ts,.js --fix",
|
|
22
24
|
"test": "vitest",
|
|
23
25
|
"test:run": "vitest run",
|
|
24
26
|
"test:coverage": "vitest run --coverage",
|
|
@@ -30,8 +32,12 @@
|
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@types/node": "^25.2.0",
|
|
32
34
|
"@vitest/coverage-v8": "^1.2.0",
|
|
35
|
+
"eslint": "^9.39.2",
|
|
36
|
+
"globals": "^17.3.0",
|
|
37
|
+
"jiti": "^2.6.1",
|
|
33
38
|
"tsup": "^8.5.1",
|
|
34
39
|
"typescript": "^5.9.3",
|
|
40
|
+
"typescript-eslint": "^8.54.0",
|
|
35
41
|
"vitest": "^1.2.0"
|
|
36
42
|
},
|
|
37
43
|
"keywords": [
|