@nodii/retry 0.0.0 → 0.1.0
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 +214 -1
- package/dist/adapters/bullmq.d.ts +38 -0
- package/dist/adapters/bullmq.d.ts.map +1 -0
- package/dist/adapters/bullmq.js +52 -0
- package/dist/adapters/bullmq.js.map +1 -0
- package/dist/attempts.d.ts +27 -0
- package/dist/attempts.d.ts.map +1 -0
- package/dist/attempts.js +69 -0
- package/dist/attempts.js.map +1 -0
- package/dist/classify.d.ts +41 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +75 -0
- package/dist/classify.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -0
- package/dist/fleet-policies.d.ts +220 -0
- package/dist/fleet-policies.d.ts.map +1 -0
- package/dist/fleet-policies.js +232 -0
- package/dist/fleet-policies.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/jitter.d.ts +47 -0
- package/dist/jitter.d.ts.map +1 -0
- package/dist/jitter.js +84 -0
- package/dist/jitter.js.map +1 -0
- package/dist/policy.d.ts +95 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +121 -0
- package/dist/policy.js.map +1 -0
- package/dist/run.d.ts +83 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +113 -0
- package/dist/run.js.map +1 -0
- package/package.json +34 -5
- package/index.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jitter.js","sourceRoot":"","sources":["../src/jitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAKrB;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,CAAS,EACT,QAAwB,EACxB,MAAgB,EAChB,GAAyE;IAEzE,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,CAAC,CAAC;QACX,KAAK,MAAM;YACT,UAAU;YACV,OAAO,MAAM,EAAE,GAAG,CAAC,CAAC;QACtB,KAAK,OAAO;YACV,qEAAqE;YACrE,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACpC,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,2DAA2D;YAC3D,2DAA2D;YAC3D,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,CAAC;YAC3B,MAAM,EAAE,GAAG,GAAG,CAAC,eAAe,GAAG,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAmB,EACnB,OAAe,EACf,IAAoD;IAEpD,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;QAC/D,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,MAAM,CAAC,WAAW;KAC5D,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,CAAC,mBAAmB;QACxC,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAmB,EACnB,KAAa,EACb,IAA4B;IAE5B,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;IAC3C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC;IAClC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;QAClD,MAAM,CAAC,GAAG,uBAAuB,CAAC,MAAM,EAAE,OAAO,EAAE;YACjD,MAAM;YACN,eAAe,EAAE,QAAQ;SAC1B,CAAC,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACZ,QAAQ,GAAG,CAAC,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the un-jittered delay grows with the attempt index.
|
|
3
|
+
*
|
|
4
|
+
* - `"exponential"` — `base · multiplier^(attempt-1)`. `multiplier: 1` gives a
|
|
5
|
+
* CONSTANT delay (the six fixed-sleep sites).
|
|
6
|
+
* - `"linear"` — `base · attempt`. Exists because
|
|
7
|
+
* `@nodii/outbox-dispatcher`'s claim query cools a row down by
|
|
8
|
+
* `interval '1 minute' * publish_attempts`, which is linear, not
|
|
9
|
+
* exponential. Modelling it as exponential would silently change the
|
|
10
|
+
* dispatcher's re-claim schedule.
|
|
11
|
+
*/
|
|
12
|
+
export type GrowthKind = "exponential" | "linear";
|
|
13
|
+
/**
|
|
14
|
+
* Jitter strategies.
|
|
15
|
+
*
|
|
16
|
+
* `d` below is the growth-computed, cap-clamped delay for the attempt.
|
|
17
|
+
*
|
|
18
|
+
* - `"none"` — `d`. What 11 of the 14 surveyed sites do today.
|
|
19
|
+
* - `"equal"` — `d/2 + U(0, d/2)` → `[d/2, d)`. **The default** (see README
|
|
20
|
+
* § "Why equal jitter").
|
|
21
|
+
* - `"full"` — `U(0, d)` → `[0, d)`. Maximum dispersal, no floor.
|
|
22
|
+
* - `{ kind: "proportional", pct }` — `d · (1 + pct·U(-1, 1))` →
|
|
23
|
+
* `[d(1-pct), d(1+pct))`. The shape both live jitter sites use
|
|
24
|
+
* (role-catalog `pct: 0.125`, replica cold-start `pct: 0.2`), so their
|
|
25
|
+
* migration can be behaviour-preserving.
|
|
26
|
+
* - `"decorrelated"` — `min(cap, U(base, 3·previous))`, seeded at `base`.
|
|
27
|
+
* A self-contained recurrence: it IGNORES `growth` / `multiplier` entirely
|
|
28
|
+
* and derives each delay from the previous one. Nothing in the fleet does
|
|
29
|
+
* this today; it is offered, never defaulted.
|
|
30
|
+
*/
|
|
31
|
+
export type JitterStrategy = "none" | "full" | "equal" | "decorrelated" | {
|
|
32
|
+
kind: "proportional";
|
|
33
|
+
pct: number;
|
|
34
|
+
};
|
|
35
|
+
export interface RetryPolicy {
|
|
36
|
+
/**
|
|
37
|
+
* Total attempts INCLUDING the first (so `3` = 1 initial + 2 retries, which
|
|
38
|
+
* is what `@nodii/onboarding` means by `MAX_ATTEMPTS = 3`).
|
|
39
|
+
*
|
|
40
|
+
* `0` = retry INDEFINITELY — `@nodii/replica-consumer`'s cold-start backfill
|
|
41
|
+
* default, where giving up means silently dropping the backfill. The cap
|
|
42
|
+
* then bounds each DELAY, never the attempt count.
|
|
43
|
+
*/
|
|
44
|
+
maxAttempts: number;
|
|
45
|
+
/** Delay before the FIRST retry, ms. Must be finite and >= 0. */
|
|
46
|
+
baseDelayMs: number;
|
|
47
|
+
/** Growth factor for `"exponential"`. `1` = constant. Ignored by `"linear"`. */
|
|
48
|
+
multiplier: number;
|
|
49
|
+
/** Ceiling on a single delay, ms. `Infinity` = uncapped. */
|
|
50
|
+
maxDelayMs: number;
|
|
51
|
+
/** How the un-jittered delay grows with the attempt index. */
|
|
52
|
+
growth: GrowthKind;
|
|
53
|
+
/** Jitter applied to the capped delay. */
|
|
54
|
+
jitter: JitterStrategy;
|
|
55
|
+
/**
|
|
56
|
+
* When `true`, jitter may push the delay ABOVE `maxDelayMs`.
|
|
57
|
+
*
|
|
58
|
+
* Both live jitter sites apply jitter AFTER the cap and never re-clamp, so
|
|
59
|
+
* `@nodii/role-catalog` can sleep 33.75s against a declared 30s cap and
|
|
60
|
+
* `@nodii/replica-consumer` can sleep 36s against the same declared cap.
|
|
61
|
+
* That is a bug: a cap that the code is allowed to exceed is not a cap. The
|
|
62
|
+
* DEFAULT here is `false` (clamp last — the cap holds unconditionally).
|
|
63
|
+
*
|
|
64
|
+
* Set `true` only to make a migration byte-for-byte behaviour-preserving
|
|
65
|
+
* against one of those two sites, and say so in the migration note.
|
|
66
|
+
*/
|
|
67
|
+
allowJitterAboveCap: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fleet default. Deliberately conservative: equal jitter (a growing FLOOR plus
|
|
71
|
+
* dispersal), a 30s ceiling (the value 3 of the 4 capped sites already use),
|
|
72
|
+
* and a finite attempt budget so a misconfigured caller cannot spin forever.
|
|
73
|
+
*/
|
|
74
|
+
export declare const DEFAULT_POLICY: RetryPolicy;
|
|
75
|
+
/** Thrown by {@link resolvePolicy} on a policy that cannot produce sane delays. */
|
|
76
|
+
export declare class InvalidRetryPolicyError extends Error {
|
|
77
|
+
constructor(message: string);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Fill a partial policy from {@link DEFAULT_POLICY} and VALIDATE it.
|
|
81
|
+
*
|
|
82
|
+
* Validation is not decoration: an unvalidated `multiplier: 0` silently turns
|
|
83
|
+
* every retry after the first into a zero-delay hot loop against a dependency
|
|
84
|
+
* that is already failing.
|
|
85
|
+
*/
|
|
86
|
+
export declare function resolvePolicy(partial?: Partial<RetryPolicy>): RetryPolicy;
|
|
87
|
+
/**
|
|
88
|
+
* The un-jittered, cap-clamped delay for a 1-based `attempt`.
|
|
89
|
+
*
|
|
90
|
+
* `attempt` is the index of the RETRY about to be scheduled: `1` is the delay
|
|
91
|
+
* between the first failure and the second try, so `attempt === 1` yields
|
|
92
|
+
* `baseDelayMs`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function baseDelayForAttempt(policy: RetryPolicy, attempt: number): number;
|
|
95
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,QAAQ,CAAC;AAElD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,MAAM,GACN,OAAO,GACP,cAAc,GACd;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,MAAM,EAAE,UAAU,CAAC;IACnB,0CAA0C;IAC1C,MAAM,EAAE,cAAc,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,WAQ5B,CAAC;AAEF,mFAAmF;AACnF,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAUD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAuDzE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,GACd,MAAM,CAuBR"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The retry policy — the ONE shape that covers the union of what the fleet's
|
|
2
|
+
// hand-rolled backoffs actually do.
|
|
3
|
+
//
|
|
4
|
+
// The requirements here are not invented. They are the union of the 14
|
|
5
|
+
// hand-rolled implementations found inside ts/* on origin/main (see README
|
|
6
|
+
// § "The survey"), which between them need:
|
|
7
|
+
//
|
|
8
|
+
// - exponential growth (approval 250ms×2, role-catalog 100ms×2,
|
|
9
|
+
// replica cold-start 1000ms×2, replica apply 100ms×2)
|
|
10
|
+
// - LINEAR growth (outbox-dispatcher's SQL cooldown is
|
|
11
|
+
// `interval '1 minute' * publish_attempts`, i.e.
|
|
12
|
+
// 1,2,3,… minutes — NOT expressible as base·mult^n)
|
|
13
|
+
// - CONSTANT growth (six fixed sleeps: 200ms, 250ms, pollMs, ttl/2, …)
|
|
14
|
+
// - a delay cap (60s / 30s / uncapped)
|
|
15
|
+
// - a max-attempts cap (3, 5, 8, 30 — and 0 = "retry indefinitely",
|
|
16
|
+
// which replica-consumer's cold start relies on)
|
|
17
|
+
// - no jitter (11 of 14 sites)
|
|
18
|
+
// - PROPORTIONAL jitter (role-catalog ±12.5%, replica cold-start ±20%)
|
|
19
|
+
//
|
|
20
|
+
// Anything not in that list is not in this type.
|
|
21
|
+
/**
|
|
22
|
+
* Fleet default. Deliberately conservative: equal jitter (a growing FLOOR plus
|
|
23
|
+
* dispersal), a 30s ceiling (the value 3 of the 4 capped sites already use),
|
|
24
|
+
* and a finite attempt budget so a misconfigured caller cannot spin forever.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_POLICY = {
|
|
27
|
+
maxAttempts: 5,
|
|
28
|
+
baseDelayMs: 100,
|
|
29
|
+
multiplier: 2,
|
|
30
|
+
maxDelayMs: 30_000,
|
|
31
|
+
growth: "exponential",
|
|
32
|
+
jitter: "equal",
|
|
33
|
+
allowJitterAboveCap: false,
|
|
34
|
+
};
|
|
35
|
+
/** Thrown by {@link resolvePolicy} on a policy that cannot produce sane delays. */
|
|
36
|
+
export class InvalidRetryPolicyError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(`invalid retry policy: ${message}`);
|
|
39
|
+
this.name = "InvalidRetryPolicyError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function requireFiniteNonNegative(name, v) {
|
|
43
|
+
if (typeof v !== "number" || Number.isNaN(v) || v < 0) {
|
|
44
|
+
throw new InvalidRetryPolicyError(`${name} must be a number >= 0 (got ${v})`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fill a partial policy from {@link DEFAULT_POLICY} and VALIDATE it.
|
|
49
|
+
*
|
|
50
|
+
* Validation is not decoration: an unvalidated `multiplier: 0` silently turns
|
|
51
|
+
* every retry after the first into a zero-delay hot loop against a dependency
|
|
52
|
+
* that is already failing.
|
|
53
|
+
*/
|
|
54
|
+
export function resolvePolicy(partial) {
|
|
55
|
+
const p = { ...DEFAULT_POLICY, ...partial };
|
|
56
|
+
if (typeof p.maxAttempts !== "number" ||
|
|
57
|
+
!Number.isInteger(p.maxAttempts) ||
|
|
58
|
+
p.maxAttempts < 0) {
|
|
59
|
+
throw new InvalidRetryPolicyError(`maxAttempts must be an integer >= 0 (0 = unbounded) (got ${p.maxAttempts})`);
|
|
60
|
+
}
|
|
61
|
+
requireFiniteNonNegative("baseDelayMs", p.baseDelayMs);
|
|
62
|
+
if (!Number.isFinite(p.baseDelayMs)) {
|
|
63
|
+
throw new InvalidRetryPolicyError("baseDelayMs must be finite");
|
|
64
|
+
}
|
|
65
|
+
requireFiniteNonNegative("maxDelayMs", p.maxDelayMs);
|
|
66
|
+
if (typeof p.multiplier !== "number" ||
|
|
67
|
+
Number.isNaN(p.multiplier) ||
|
|
68
|
+
p.multiplier < 1) {
|
|
69
|
+
throw new InvalidRetryPolicyError(`multiplier must be >= 1 (got ${p.multiplier}) — a multiplier < 1 SHRINKS the delay each attempt, turning backoff into a hot loop`);
|
|
70
|
+
}
|
|
71
|
+
if (p.growth !== "exponential" && p.growth !== "linear") {
|
|
72
|
+
throw new InvalidRetryPolicyError(`growth must be "exponential" | "linear" (got ${String(p.growth)})`);
|
|
73
|
+
}
|
|
74
|
+
if (typeof p.jitter === "object") {
|
|
75
|
+
if (p.jitter === null || p.jitter.kind !== "proportional") {
|
|
76
|
+
throw new InvalidRetryPolicyError(`unknown jitter strategy ${JSON.stringify(p.jitter)}`);
|
|
77
|
+
}
|
|
78
|
+
const { pct } = p.jitter;
|
|
79
|
+
if (typeof pct !== "number" || Number.isNaN(pct) || pct < 0 || pct > 1) {
|
|
80
|
+
throw new InvalidRetryPolicyError(`proportional jitter pct must be a FRACTION in [0, 1] (got ${pct}) — note @nodii/replica-consumer carries two conflicting units for this field: start.ts uses 0.2 and define-replica.ts/init.ts use 25`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (p.jitter !== "none" &&
|
|
84
|
+
p.jitter !== "full" &&
|
|
85
|
+
p.jitter !== "equal" &&
|
|
86
|
+
p.jitter !== "decorrelated") {
|
|
87
|
+
throw new InvalidRetryPolicyError(`unknown jitter strategy ${String(p.jitter)}`);
|
|
88
|
+
}
|
|
89
|
+
return p;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The un-jittered, cap-clamped delay for a 1-based `attempt`.
|
|
93
|
+
*
|
|
94
|
+
* `attempt` is the index of the RETRY about to be scheduled: `1` is the delay
|
|
95
|
+
* between the first failure and the second try, so `attempt === 1` yields
|
|
96
|
+
* `baseDelayMs`.
|
|
97
|
+
*/
|
|
98
|
+
export function baseDelayForAttempt(policy, attempt) {
|
|
99
|
+
if (!Number.isInteger(attempt) || attempt < 1) {
|
|
100
|
+
throw new InvalidRetryPolicyError(`attempt must be an integer >= 1 (got ${attempt})`);
|
|
101
|
+
}
|
|
102
|
+
// A zero base is a zero delay at every attempt — `0 · multiplier^n` is 0 for
|
|
103
|
+
// any finite factor. Handled FIRST because `multiplier^n` overflows to
|
|
104
|
+
// Infinity for large n and `0 * Infinity` is NaN, which the guard below
|
|
105
|
+
// would otherwise resolve to the CAP. (Caught by its own test: the
|
|
106
|
+
// counting-only sites — replica-consumer + telemetry apply retries — declare
|
|
107
|
+
// baseDelayMs 0 because their spacing comes from Redis PEL redelivery, and
|
|
108
|
+
// silently promoting that to a 30s cap would be a fabricated sleep.)
|
|
109
|
+
if (policy.baseDelayMs === 0)
|
|
110
|
+
return 0;
|
|
111
|
+
const raw = policy.growth === "linear"
|
|
112
|
+
? policy.baseDelayMs * attempt
|
|
113
|
+
: policy.baseDelayMs * policy.multiplier ** (attempt - 1);
|
|
114
|
+
// Genuine overflow: `base · mult^n` exceeds Number.MAX_VALUE for large n.
|
|
115
|
+
// The sequence is monotone non-decreasing, so anything past the cap IS the
|
|
116
|
+
// cap.
|
|
117
|
+
if (!Number.isFinite(raw))
|
|
118
|
+
return policy.maxDelayMs;
|
|
119
|
+
return Math.min(raw, policy.maxDelayMs);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type ShouldRetry } from "./classify.js";
|
|
2
|
+
import { type RandomFn } from "./jitter.js";
|
|
3
|
+
import { type RetryPolicy } from "./policy.js";
|
|
4
|
+
/**
|
|
5
|
+
* Injectable sleep — THE clock seam. Tests pass a recorder that returns
|
|
6
|
+
* immediately, so a suite that exercises a 30s cap still finishes in
|
|
7
|
+
* milliseconds. A test suite that actually waits 30s is a test suite that
|
|
8
|
+
* gets muted.
|
|
9
|
+
*/
|
|
10
|
+
export type SleepFn = (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
11
|
+
/** Default sleep: `setTimeout`, `unref`'d where available, abort-aware. */
|
|
12
|
+
export declare const defaultSleep: SleepFn;
|
|
13
|
+
/** State passed into each attempt. */
|
|
14
|
+
export interface AttemptContext {
|
|
15
|
+
/** 1-based index of the attempt about to run. */
|
|
16
|
+
attempt: number;
|
|
17
|
+
/**
|
|
18
|
+
* Is this the LAST attempt the budget allows? Always `false` when
|
|
19
|
+
* `maxAttempts === 0` (unbounded). Correct at the boundary — see
|
|
20
|
+
* `./attempts.ts` for why that sentence needs saying.
|
|
21
|
+
*/
|
|
22
|
+
isFinalAttempt: boolean;
|
|
23
|
+
/** Retries left after this one; `Infinity` when unbounded. */
|
|
24
|
+
remaining: number;
|
|
25
|
+
/** The error that ended the previous attempt, if any. */
|
|
26
|
+
previousError: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Payload for the `onRetry` telemetry hook. */
|
|
29
|
+
export interface RetryEvent {
|
|
30
|
+
/** 1-based index of the attempt that just FAILED. */
|
|
31
|
+
attempt: number;
|
|
32
|
+
/** 1-based index of the attempt that will run after the delay. */
|
|
33
|
+
nextAttempt: number;
|
|
34
|
+
/** The delay actually slept, ms (post-jitter, post-clamp). */
|
|
35
|
+
delayMs: number;
|
|
36
|
+
/** The error that triggered the retry. */
|
|
37
|
+
error: unknown;
|
|
38
|
+
}
|
|
39
|
+
/** Payload for the `onExhausted` hook. */
|
|
40
|
+
export interface ExhaustedEvent {
|
|
41
|
+
/** Attempts actually made. */
|
|
42
|
+
attempts: number;
|
|
43
|
+
/** The final error. */
|
|
44
|
+
error: unknown;
|
|
45
|
+
}
|
|
46
|
+
export interface RetryOptions {
|
|
47
|
+
/** Partial policy; unset fields come from `DEFAULT_POLICY`. */
|
|
48
|
+
policy?: Partial<RetryPolicy>;
|
|
49
|
+
/** Terminal-vs-transient seam. Default {@link retryAll}. */
|
|
50
|
+
shouldRetry?: ShouldRetry;
|
|
51
|
+
/** Fired BEFORE each backoff sleep. Telemetry only — it cannot veto. */
|
|
52
|
+
onRetry?: (event: RetryEvent) => void | Promise<void>;
|
|
53
|
+
/** Fired once when the attempt budget is spent. */
|
|
54
|
+
onExhausted?: (event: ExhaustedEvent) => void | Promise<void>;
|
|
55
|
+
/** Clock seam. Default {@link defaultSleep}. */
|
|
56
|
+
sleep?: SleepFn;
|
|
57
|
+
/** RNG seam for jitter. Default `Math.random`. */
|
|
58
|
+
random?: RandomFn;
|
|
59
|
+
/** Aborts the backoff sleep AND stops further attempts. */
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
/**
|
|
62
|
+
* Wrap the final error in {@link RetryExhaustedError}. Default `false` —
|
|
63
|
+
* the LAST error is rethrown as-is so caller `instanceof` checks survive.
|
|
64
|
+
*/
|
|
65
|
+
wrapExhausted?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run `fn` under a retry policy.
|
|
69
|
+
*
|
|
70
|
+
* Guarantees, each of which has a test that goes red when the guard is broken:
|
|
71
|
+
* - exactly `maxAttempts` invocations of `fn` in the worst case
|
|
72
|
+
* (`maxAttempts: 0` = unbounded);
|
|
73
|
+
* - exactly `attempts - 1` sleeps, whose durations match
|
|
74
|
+
* `backoffSequence(policy, …)`;
|
|
75
|
+
* - NO sleep after the final attempt (a trailing sleep is a wasted
|
|
76
|
+
* shutdown-delay and hides the failure by the length of the cap);
|
|
77
|
+
* - `shouldRetry === false` short-circuits with ZERO further sleeps;
|
|
78
|
+
* - `NonRetryableError` bypasses `shouldRetry` entirely;
|
|
79
|
+
* - an `onRetry` that throws never converts a retryable failure into a
|
|
80
|
+
* crash (telemetry must not be load-bearing).
|
|
81
|
+
*/
|
|
82
|
+
export declare function retry<T>(fn: (ctx: AttemptContext) => T | Promise<T>, opts?: RetryOptions): Promise<T>;
|
|
83
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAY,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,KAAK,QAAQ,EAA2B,MAAM,aAAa,CAAC;AAErE,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,aAAa,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1E,2EAA2E;AAC3E,eAAO,MAAM,YAAY,EAAE,OAkBvB,CAAC;AAEL,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,4DAA4D;IAC5D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,mDAAmD;IACnD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC3C,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,CAAC,CAAC,CAgFZ"}
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { retryAll } from "./classify.js";
|
|
2
|
+
import { NonRetryableError, RetryExhaustedError } from "./errors.js";
|
|
3
|
+
import { jitteredDelayForAttempt } from "./jitter.js";
|
|
4
|
+
import { isFinalAttempt, remainingAttempts } from "./attempts.js";
|
|
5
|
+
import { resolvePolicy } from "./policy.js";
|
|
6
|
+
/** Default sleep: `setTimeout`, `unref`'d where available, abort-aware. */
|
|
7
|
+
export const defaultSleep = (ms, signal) => new Promise((resolve) => {
|
|
8
|
+
if (signal?.aborted) {
|
|
9
|
+
resolve();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const t = setTimeout(resolve, ms);
|
|
13
|
+
// Node/Bun timers expose unref(); browsers do not. Never let a pending
|
|
14
|
+
// backoff hold a process open past its work.
|
|
15
|
+
t.unref?.();
|
|
16
|
+
signal?.addEventListener("abort", () => {
|
|
17
|
+
clearTimeout(t);
|
|
18
|
+
resolve();
|
|
19
|
+
}, { once: true });
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Run `fn` under a retry policy.
|
|
23
|
+
*
|
|
24
|
+
* Guarantees, each of which has a test that goes red when the guard is broken:
|
|
25
|
+
* - exactly `maxAttempts` invocations of `fn` in the worst case
|
|
26
|
+
* (`maxAttempts: 0` = unbounded);
|
|
27
|
+
* - exactly `attempts - 1` sleeps, whose durations match
|
|
28
|
+
* `backoffSequence(policy, …)`;
|
|
29
|
+
* - NO sleep after the final attempt (a trailing sleep is a wasted
|
|
30
|
+
* shutdown-delay and hides the failure by the length of the cap);
|
|
31
|
+
* - `shouldRetry === false` short-circuits with ZERO further sleeps;
|
|
32
|
+
* - `NonRetryableError` bypasses `shouldRetry` entirely;
|
|
33
|
+
* - an `onRetry` that throws never converts a retryable failure into a
|
|
34
|
+
* crash (telemetry must not be load-bearing).
|
|
35
|
+
*/
|
|
36
|
+
export async function retry(fn, opts = {}) {
|
|
37
|
+
const policy = resolvePolicy(opts.policy);
|
|
38
|
+
const shouldRetry = opts.shouldRetry ?? retryAll;
|
|
39
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
40
|
+
const random = opts.random ?? Math.random;
|
|
41
|
+
let previousError = undefined;
|
|
42
|
+
let previousDelayMs = policy.baseDelayMs;
|
|
43
|
+
// ONE termination guard, deliberately. An earlier draft also bounded the
|
|
44
|
+
// loop with `attempt <= maxAttempts`; mutation testing proved that condition
|
|
45
|
+
// unreachable — `isFinalAttempt` always breaks first — i.e. it was dead code
|
|
46
|
+
// that no test could kill. Two guards for one invariant means one of them is
|
|
47
|
+
// untested by construction, so the redundant one is gone. `isFinalAttempt`
|
|
48
|
+
// is `false` forever when `maxAttempts === 0`, which IS the unbounded case.
|
|
49
|
+
for (let attempt = 1;; attempt++) {
|
|
50
|
+
const final = isFinalAttempt(attempt, policy.maxAttempts);
|
|
51
|
+
try {
|
|
52
|
+
return await fn({
|
|
53
|
+
attempt,
|
|
54
|
+
isFinalAttempt: final,
|
|
55
|
+
remaining: remainingAttempts(attempt, policy.maxAttempts),
|
|
56
|
+
previousError,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
previousError = err;
|
|
61
|
+
// The operation's own terminal verdict outranks the caller's predicate.
|
|
62
|
+
if (err instanceof NonRetryableError)
|
|
63
|
+
throw err;
|
|
64
|
+
if (final)
|
|
65
|
+
break;
|
|
66
|
+
if (!shouldRetry(err, { attempt, maxAttempts: policy.maxAttempts })) {
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
if (opts.signal?.aborted)
|
|
70
|
+
throw err;
|
|
71
|
+
const delayMs = jitteredDelayForAttempt(policy, attempt, {
|
|
72
|
+
random,
|
|
73
|
+
previousDelayMs,
|
|
74
|
+
});
|
|
75
|
+
previousDelayMs = delayMs;
|
|
76
|
+
if (opts.onRetry) {
|
|
77
|
+
try {
|
|
78
|
+
await opts.onRetry({
|
|
79
|
+
attempt,
|
|
80
|
+
nextAttempt: attempt + 1,
|
|
81
|
+
delayMs,
|
|
82
|
+
error: err,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Telemetry is never load-bearing: a failing metrics sink must not
|
|
87
|
+
// turn a retryable error into a crash. Same posture as
|
|
88
|
+
// replica-consumer's guarded audit emit.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
await sleep(delayMs, opts.signal);
|
|
92
|
+
// Aborted DURING the sleep — stop, surface the last real error rather
|
|
93
|
+
// than inventing an abort error the caller has no handler for.
|
|
94
|
+
if (opts.signal?.aborted)
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Budget spent.
|
|
99
|
+
const attempts = policy.maxAttempts;
|
|
100
|
+
if (opts.onExhausted) {
|
|
101
|
+
try {
|
|
102
|
+
await opts.onExhausted({ attempts, error: previousError });
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// as above
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (opts.wrapExhausted) {
|
|
109
|
+
throw new RetryExhaustedError(attempts, previousError);
|
|
110
|
+
}
|
|
111
|
+
throw previousError;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodii/retry",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The retry / exponential-backoff / jitter policy for the Nodii microservice stack, as a real package. Consolidates 14 hand-rolled backoff implementations that had drifted across the shared libraries themselves (250ms/2x/60s no-jitter, 100ms/2x/30s ±12.5%, 1000ms/2x/30s ±20%, a linear 1-min-per-attempt SQL cooldown, and six fixed sleeps) into one injectable-clock policy with a shouldRetry seam, an onRetry telemetry hook, and a BullMQ attemptsMade post-increment adapter.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
7
19
|
"publishConfig": {
|
|
8
20
|
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "bun test"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/bun": "^1.3.13",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"bun": ">=1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/cognion-nucleus/nodii-libs.git",
|
|
37
|
+
"directory": "ts/retry"
|
|
9
38
|
}
|
|
10
|
-
}
|
|
39
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// placeholder — real package ships from nodii-libs release.yml
|