@sdxc/backoff 0.0.0-stage → 2026.10.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +215 -2
- package/dist/backoff.d.ts +77 -0
- package/dist/backoff.js +80 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -0
- package/package.json +31 -5
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergio Xalambrí
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,216 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sdxc/backoff
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Retry delay schedules with exponential, linear, constant or stepped growth, a ceiling, free attempts and seedable jitter.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm add @sdxc/backoff
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Durations are [`@sdxc/duration`](https://www.npmjs.com/package/@sdxc/duration) inputs, and jitter draws from a [`@sdxc/random`](https://www.npmjs.com/package/@sdxc/random) stream; install `@sdxc/random` too when you pass a seeded one.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Build a schedule
|
|
16
|
+
|
|
17
|
+
`delay(attempt)` answers the milliseconds to wait after `attempt` failures, counting from 1.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createBackoff } from "@sdxc/backoff";
|
|
21
|
+
|
|
22
|
+
let backoff = createBackoff({ base: "15 seconds", max: "12 hours" });
|
|
23
|
+
|
|
24
|
+
backoff.delay(1); // 15_000
|
|
25
|
+
backoff.delay(3); // 60_000
|
|
26
|
+
backoff.delay(20); // 43_200_000, the ceiling
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
That schedule is `Math.min(15_000 * 2 ** (attempt - 1), 43_200_000)`.
|
|
30
|
+
|
|
31
|
+
### Pick the growth
|
|
32
|
+
|
|
33
|
+
`growth` changes the curve, and `steps` replaces it with a table whose last entry repeats.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
createBackoff({ base: "1 second", factor: 3 }); // 1s, 3s, 9s, 27s…
|
|
37
|
+
createBackoff({ base: "30 seconds", growth: "linear" }); // 30s, 60s, 90s…
|
|
38
|
+
createBackoff({ base: "5 seconds", growth: "constant" }); // 5s, 5s, 5s…
|
|
39
|
+
createBackoff({ steps: ["15 seconds", "1 minute", "5 minutes", "2 hours"] }); // …2h, 2h
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Spread retries with jitter
|
|
43
|
+
|
|
44
|
+
A fraction `f` draws each delay from `delay × (1 ± f)`; `"full"` draws from `[0, delay]`. Jitter applies after `max`, so retries waiting at the ceiling still spread out.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
let backoff = createBackoff({ base: "15 seconds", max: "12 hours", jitter: 0.2 });
|
|
48
|
+
|
|
49
|
+
backoff.delay(1); // between 12_000 and 18_000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Allow free attempts
|
|
53
|
+
|
|
54
|
+
The first `free` failures answer `0`, and the curve starts on the failure after them.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
let lockout = createBackoff({ free: 3, base: "1 second", max: "15 minutes" });
|
|
58
|
+
|
|
59
|
+
lockout.delay(3); // 0
|
|
60
|
+
lockout.delay(4); // 1_000
|
|
61
|
+
lockout.delay(5); // 2_000
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
### `createBackoff(options: BackoffOptions): Backoff`
|
|
67
|
+
|
|
68
|
+
Validates the options once and returns the schedule, so `delay` and `at` never fail. Throws `RangeError` for a negative or unparseable duration, a `factor` below 1, a `max` below `base`, a `jitter` outside `[0, 1]`, empty `steps`, or a `free` that is not a non-negative integer.
|
|
69
|
+
|
|
70
|
+
A curve schedule takes:
|
|
71
|
+
|
|
72
|
+
- `base`: the delay after the first counted failure
|
|
73
|
+
- `growth`: `"exponential"` (default) multiplies by `factor` per failure, `"linear"` adds `base`, `"constant"` repeats `base`
|
|
74
|
+
- `factor`: the exponential multiplier, `2` by default
|
|
75
|
+
- `max`: the ceiling before jitter; unbounded when omitted
|
|
76
|
+
|
|
77
|
+
A stepped schedule takes `steps`, one delay per counted failure. Both take:
|
|
78
|
+
|
|
79
|
+
- `free`: failures that answer `0`, `0` by default
|
|
80
|
+
- `jitter`: a `Jitter`; none when omitted. Jittered delays round to whole milliseconds
|
|
81
|
+
- `random`: the stream jitter draws from, `systemRandom()` by default
|
|
82
|
+
|
|
83
|
+
### `Backoff`
|
|
84
|
+
|
|
85
|
+
- `delay(attempt: number): number`: milliseconds after `attempt` failures; `0` for a non-positive or free attempt
|
|
86
|
+
- `at(attempt: number, now: number): number`: `now + delay(attempt)`, for storing when to try again
|
|
87
|
+
|
|
88
|
+
### Types
|
|
89
|
+
|
|
90
|
+
#### `BackoffOptions`
|
|
91
|
+
|
|
92
|
+
`BackoffCurveOptions | BackoffStepsOptions`, both extending `BackoffSharedOptions` (`free`, `jitter`, `random`).
|
|
93
|
+
|
|
94
|
+
#### `Jitter`
|
|
95
|
+
|
|
96
|
+
`number | "full"`: a fraction in `[0, 1]`, or a draw from zero to the full delay.
|
|
97
|
+
|
|
98
|
+
## Pattern: Retrying A Queue Message
|
|
99
|
+
|
|
100
|
+
A queue consumer passes its delivery count straight in, so early retries come quickly and a receiver that stays down is left alone.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { createBackoff } from "@sdxc/backoff";
|
|
104
|
+
|
|
105
|
+
let deliveryBackoff = createBackoff({ base: "1 minute", max: "6 hours", jitter: 0.2 });
|
|
106
|
+
|
|
107
|
+
export default {
|
|
108
|
+
async queue(batch: MessageBatch<{ url: string }>) {
|
|
109
|
+
for (let message of batch.messages) {
|
|
110
|
+
let response = await fetch(message.body.url, { method: "POST" });
|
|
111
|
+
if (response.ok) message.ack();
|
|
112
|
+
else
|
|
113
|
+
message.retry({ delaySeconds: Math.ceil(deliveryBackoff.delay(message.attempts) / 1000) });
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Pattern: Retrying A Request And Honoring Retry-After
|
|
120
|
+
|
|
121
|
+
When a server says how long to wait, wait at least that long. `Retry-After` is either a number of seconds or an HTTP date; anything else counts as no answer.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { createBackoff } from "@sdxc/backoff";
|
|
125
|
+
|
|
126
|
+
let backoff = createBackoff({ base: "1 second", max: "5 minutes", jitter: "full" });
|
|
127
|
+
|
|
128
|
+
function retryAfterMs(response: Response, now: number): number {
|
|
129
|
+
let header = response.headers.get("retry-after");
|
|
130
|
+
if (header === null) return 0;
|
|
131
|
+
|
|
132
|
+
let seconds = Number(header);
|
|
133
|
+
if (Number.isFinite(seconds)) return Math.max(seconds * 1000, 0);
|
|
134
|
+
|
|
135
|
+
let date = Date.parse(header);
|
|
136
|
+
return Number.isNaN(date) ? 0 : Math.max(date - now, 0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function fetchWithRetry(url: string, attempts = 5): Promise<Response> {
|
|
140
|
+
for (let attempt = 1; ; attempt++) {
|
|
141
|
+
let response = await fetch(url);
|
|
142
|
+
if (response.ok || attempt === attempts) return response;
|
|
143
|
+
let wait = Math.max(retryAfterMs(response, Date.now()), backoff.delay(attempt));
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Pattern: Locking Out Repeated Sign-In Failures
|
|
150
|
+
|
|
151
|
+
A stored failure count and `at()` give a row the time its next attempt opens. The first three failures cost nothing.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { createBackoff } from "@sdxc/backoff";
|
|
155
|
+
|
|
156
|
+
let lockout = createBackoff({ free: 3, steps: ["30 seconds", "5 minutes", "1 hour"] });
|
|
157
|
+
|
|
158
|
+
interface SignInState {
|
|
159
|
+
failures: number;
|
|
160
|
+
lockedUntil: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isLocked(state: SignInState, now: number): boolean {
|
|
164
|
+
return state.lockedUntil > now;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function recordFailure(state: SignInState, now: number): SignInState {
|
|
168
|
+
let failures = state.failures + 1;
|
|
169
|
+
return { failures, lockedUntil: lockout.at(failures, now) };
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Pattern: Exact Delays In Tests
|
|
174
|
+
|
|
175
|
+
Pass a seeded `Random` and every jittered delay is reproducible, so a test asserts exact values.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { createBackoff } from "@sdxc/backoff";
|
|
179
|
+
import { createRandom } from "@sdxc/random";
|
|
180
|
+
import { expect, test } from "vitest";
|
|
181
|
+
|
|
182
|
+
test("jitter is reproducible", () => {
|
|
183
|
+
let first = createBackoff({ base: 1000, jitter: 0.2, random: createRandom("test") });
|
|
184
|
+
let second = createBackoff({ base: 1000, jitter: 0.2, random: createRandom("test") });
|
|
185
|
+
|
|
186
|
+
expect(first.delay(3)).toBe(second.delay(3));
|
|
187
|
+
expect(first.delay(3)).toBeGreaterThanOrEqual(3200);
|
|
188
|
+
expect(first.delay(3)).toBeLessThanOrEqual(4800);
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Versioning
|
|
193
|
+
|
|
194
|
+
Releases are dated rather than semantic. A version is the UTC date it was published, written `YYYY.M.D`, so `2026.9.4` is the release from 4 September 2026. At most one release goes out per day.
|
|
195
|
+
|
|
196
|
+
Those numbers say when, not what: a later date means a later release and carries no compatibility promise. Any release may change or remove an export.
|
|
197
|
+
|
|
198
|
+
Depend on one exact date, and move it when you are ready to take the change:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"dependencies": {
|
|
203
|
+
"@sdxc/backoff": "2026.9.4"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
A caret or tilde range reads the date as major, minor and patch, so it accepts every later release in the same year. An exact version keeps the upgrade yours to schedule.
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT
|
|
213
|
+
|
|
214
|
+
## Author
|
|
215
|
+
|
|
216
|
+
[Sergio Xalambrí](https://sergiodxa.com)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry delay schedules: how long to wait after the Nth failure, from a growth
|
|
3
|
+
* curve or a hand-written table, with a ceiling, free attempts and jitter drawn
|
|
4
|
+
* from an injectable `Random`, so a seeded test asserts exact delays.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import type { DurationInput } from "@sdxc/duration";
|
|
10
|
+
import type { Random } from "@sdxc/random";
|
|
11
|
+
/** A schedule answering how long to wait after a given number of failures. */
|
|
12
|
+
export interface Backoff {
|
|
13
|
+
/**
|
|
14
|
+
* Milliseconds to wait after `attempt` failures, counting from 1. A
|
|
15
|
+
* non-positive attempt and a free attempt both answer `0`.
|
|
16
|
+
*/
|
|
17
|
+
delay(attempt: number): number;
|
|
18
|
+
/** The epoch milliseconds of the next try: `now` plus `delay(attempt)`. */
|
|
19
|
+
at(attempt: number, now: number): number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* How jitter spreads a delay: a fraction in `[0, 1]` draws from
|
|
23
|
+
* `delay × (1 ± fraction)`, and `"full"` draws from `[0, delay]`.
|
|
24
|
+
*/
|
|
25
|
+
export type Jitter = number | "full";
|
|
26
|
+
/** Options every schedule accepts, whatever shapes its delays. */
|
|
27
|
+
export interface BackoffSharedOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Failures that answer `0`; the schedule's first delay belongs to the
|
|
30
|
+
* failure right after them.
|
|
31
|
+
* @default 0
|
|
32
|
+
*/
|
|
33
|
+
free?: number;
|
|
34
|
+
/** Spread applied after the ceiling, so retries waiting at the cap still spread out. */
|
|
35
|
+
jitter?: Jitter;
|
|
36
|
+
/**
|
|
37
|
+
* The stream jitter draws from; pass a seeded one to make delays exact.
|
|
38
|
+
* @default systemRandom()
|
|
39
|
+
*/
|
|
40
|
+
random?: Random;
|
|
41
|
+
}
|
|
42
|
+
/** A schedule computed from a base delay and a growth rule. */
|
|
43
|
+
export interface BackoffCurveOptions extends BackoffSharedOptions {
|
|
44
|
+
/** The delay after the first counted failure. */
|
|
45
|
+
base: DurationInput;
|
|
46
|
+
/**
|
|
47
|
+
* `"exponential"` multiplies by `factor` per failure, `"linear"` adds `base`
|
|
48
|
+
* per failure, and `"constant"` repeats `base`.
|
|
49
|
+
* @default "exponential"
|
|
50
|
+
*/
|
|
51
|
+
growth?: "exponential" | "linear" | "constant";
|
|
52
|
+
/**
|
|
53
|
+
* The multiplier per failure of an exponential schedule.
|
|
54
|
+
* @default 2
|
|
55
|
+
*/
|
|
56
|
+
factor?: number;
|
|
57
|
+
/** The ceiling a delay reaches before jitter; unbounded when omitted. */
|
|
58
|
+
max?: DurationInput;
|
|
59
|
+
}
|
|
60
|
+
/** A schedule chosen by hand, one step per failure, the last one repeating. */
|
|
61
|
+
export interface BackoffStepsOptions extends BackoffSharedOptions {
|
|
62
|
+
/** The delay after each counted failure, in order. */
|
|
63
|
+
steps: readonly DurationInput[];
|
|
64
|
+
}
|
|
65
|
+
/** Either shape `createBackoff` accepts. */
|
|
66
|
+
export type BackoffOptions = BackoffCurveOptions | BackoffStepsOptions;
|
|
67
|
+
/**
|
|
68
|
+
* Build a schedule. Options are validated once here, so `delay` and `at`
|
|
69
|
+
* never fail afterwards.
|
|
70
|
+
*
|
|
71
|
+
* @throws RangeError for a negative or unparseable base, step or ceiling, a
|
|
72
|
+
* `factor` below 1, a `max` below `base`, a `jitter` outside `[0, 1]`, empty
|
|
73
|
+
* `steps`, or a `free` that is not a non-negative integer.
|
|
74
|
+
* @example createBackoff({ base: "15 seconds", max: "12 hours", jitter: 0.2 }).delay(3); // 60_000 ± 20%
|
|
75
|
+
* @example createBackoff({ steps: ["1 minute", "5 minutes", "1 hour"] }).at(attempts, Date.now());
|
|
76
|
+
*/
|
|
77
|
+
export declare function createBackoff(options: BackoffOptions): Backoff;
|
package/dist/backoff.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { toMs } from "@sdxc/duration";
|
|
2
|
+
import { systemRandom } from "@sdxc/random";
|
|
3
|
+
/**
|
|
4
|
+
* Build a schedule. Options are validated once here, so `delay` and `at`
|
|
5
|
+
* never fail afterwards.
|
|
6
|
+
*
|
|
7
|
+
* @throws RangeError for a negative or unparseable base, step or ceiling, a
|
|
8
|
+
* `factor` below 1, a `max` below `base`, a `jitter` outside `[0, 1]`, empty
|
|
9
|
+
* `steps`, or a `free` that is not a non-negative integer.
|
|
10
|
+
* @example createBackoff({ base: "15 seconds", max: "12 hours", jitter: 0.2 }).delay(3); // 60_000 ± 20%
|
|
11
|
+
* @example createBackoff({ steps: ["1 minute", "5 minutes", "1 hour"] }).at(attempts, Date.now());
|
|
12
|
+
*/
|
|
13
|
+
export function createBackoff(options) {
|
|
14
|
+
let free = options.free ?? 0;
|
|
15
|
+
if (!Number.isSafeInteger(free) || free < 0) {
|
|
16
|
+
throw new RangeError(`free needs a non-negative integer, received ${free}.`);
|
|
17
|
+
}
|
|
18
|
+
let jitter = options.jitter;
|
|
19
|
+
if (typeof jitter === "number" && !(jitter >= 0 && jitter <= 1)) {
|
|
20
|
+
throw new RangeError(`jitter needs a fraction in [0, 1] or "full", received ${jitter}.`);
|
|
21
|
+
}
|
|
22
|
+
let random = options.random ?? systemRandom();
|
|
23
|
+
let curve = "steps" in options ? stepsCurve(options) : growthCurve(options);
|
|
24
|
+
let backoff = {
|
|
25
|
+
delay(attempt) {
|
|
26
|
+
let counted = attempt - free;
|
|
27
|
+
if (!(counted >= 1))
|
|
28
|
+
return 0;
|
|
29
|
+
let delay = curve(counted);
|
|
30
|
+
if (jitter === undefined || jitter === 0)
|
|
31
|
+
return delay;
|
|
32
|
+
if (jitter === "full")
|
|
33
|
+
return Math.round(random.float(0, delay));
|
|
34
|
+
return Math.round(delay * random.float(1 - jitter, 1 + jitter));
|
|
35
|
+
},
|
|
36
|
+
at(attempt, now) {
|
|
37
|
+
return now + backoff.delay(attempt);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
return backoff;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Convert a duration option to milliseconds, rejecting the negative values
|
|
44
|
+
* and unparseable strings that `toMs` reports as `NaN`.
|
|
45
|
+
*/
|
|
46
|
+
function nonNegativeMs(name, input) {
|
|
47
|
+
let ms = toMs(input);
|
|
48
|
+
if (!(ms >= 0))
|
|
49
|
+
throw new RangeError(`${name} needs a non-negative duration, received ${input}.`);
|
|
50
|
+
return ms;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The pre-jitter delay of a growth schedule for a counted failure (1 or more).
|
|
54
|
+
* An unbounded exponential schedule reaches `Infinity` once the product
|
|
55
|
+
* overflows.
|
|
56
|
+
*/
|
|
57
|
+
function growthCurve(options) {
|
|
58
|
+
let base = nonNegativeMs("base", options.base);
|
|
59
|
+
let factor = options.factor ?? 2;
|
|
60
|
+
if (!(factor >= 1))
|
|
61
|
+
throw new RangeError(`factor needs to be at least 1, received ${factor}.`);
|
|
62
|
+
let max = options.max === undefined ? Infinity : nonNegativeMs("max", options.max);
|
|
63
|
+
if (max < base)
|
|
64
|
+
throw new RangeError(`max (${max}ms) needs to be at least base (${base}ms).`);
|
|
65
|
+
let growth = options.growth ?? "exponential";
|
|
66
|
+
return (counted) => {
|
|
67
|
+
if (growth === "constant")
|
|
68
|
+
return Math.min(base, max);
|
|
69
|
+
if (growth === "linear")
|
|
70
|
+
return Math.min(base * counted, max);
|
|
71
|
+
return Math.min(base * factor ** (counted - 1), max);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** The pre-jitter delay of a stepped schedule, repeating the last step past the end. */
|
|
75
|
+
function stepsCurve(options) {
|
|
76
|
+
if (options.steps.length === 0)
|
|
77
|
+
throw new RangeError("steps needs at least one delay.");
|
|
78
|
+
let steps = options.steps.map((step, index) => nonNegativeMs(`steps[${index}]`, step));
|
|
79
|
+
return (counted) => steps[Math.min(counted, steps.length) - 1];
|
|
80
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package entrypoint: `createBackoff` and the schedule it returns, which
|
|
3
|
+
* answers how long to wait after the Nth failure in milliseconds, plus the
|
|
4
|
+
* option shapes a caller builds one from.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
export type { Backoff, BackoffCurveOptions, BackoffOptions, BackoffSharedOptions, BackoffStepsOptions, Jitter, } from "./backoff.js";
|
|
10
|
+
export { createBackoff } from "./backoff.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createBackoff } from "./backoff.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@sdxc/backoff",
|
|
3
|
+
"version": "2026.10.13",
|
|
4
|
+
"description": "Retry delay schedules with exponential, linear, constant or stepped growth, a ceiling, free attempts and seedable jitter",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@sdxc/duration": "2026.10.13",
|
|
12
|
+
"@sdxc/random": "2026.10.13"
|
|
13
|
+
},
|
|
14
|
+
"gitHead": "5ff758a57e35480a8db7abd24f1d9527b81bfb51",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/sergiodxa/monorepo.git",
|
|
21
|
+
"directory": "packages/backoff"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://sdxc.sergiodxa.com/api/backoff",
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "Sergio Xalambrí",
|
|
26
|
+
"url": "https://sergiodxa.com"
|
|
27
|
+
},
|
|
28
|
+
"funding": {
|
|
29
|
+
"type": "github",
|
|
30
|
+
"url": "https://github.com/sponsors/sergiodxa"
|
|
31
|
+
}
|
|
32
|
+
}
|