@hardlydifficult/throttle 1.0.0 → 3.0.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 +79 -18
- package/dist/Throttle.d.ts +13 -6
- package/dist/Throttle.d.ts.map +1 -1
- package/dist/Throttle.js +36 -14
- package/dist/Throttle.js.map +1 -1
- package/dist/ThrottledUpdater.d.ts +43 -0
- package/dist/ThrottledUpdater.d.ts.map +1 -0
- package/dist/ThrottledUpdater.js +100 -0
- package/dist/ThrottledUpdater.js.map +1 -0
- package/dist/backoff.d.ts +28 -0
- package/dist/backoff.d.ts.map +1 -0
- package/dist/backoff.js +40 -0
- package/dist/backoff.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/isConnectionError.d.ts +9 -0
- package/dist/isConnectionError.d.ts.map +1 -0
- package/dist/isConnectionError.js +46 -0
- package/dist/isConnectionError.js.map +1 -0
- package/package.json +3 -5
- package/dist/WeightedThrottle.d.ts +0 -20
- package/dist/WeightedThrottle.d.ts.map +0 -1
- package/dist/WeightedThrottle.js +0 -53
- package/dist/WeightedThrottle.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hardlydifficult/throttle
|
|
2
2
|
|
|
3
|
-
Rate limiting
|
|
3
|
+
Rate limiting, exponential backoff, connection error detection, and throttled message updates.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,35 +8,96 @@ Rate limiting utilities with optional state persistence.
|
|
|
8
8
|
npm install @hardlydifficult/throttle
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## API
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
### `Throttle`
|
|
14
|
+
|
|
15
|
+
Token-bucket rate limiter with optional persistent state.
|
|
14
16
|
|
|
15
17
|
```typescript
|
|
16
|
-
import { Throttle } from
|
|
18
|
+
import { Throttle } from "@hardlydifficult/throttle";
|
|
17
19
|
|
|
18
20
|
const throttle = new Throttle({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
unitsPerSecond: 10,
|
|
22
|
+
persistKey: "github-api", // optional: persist across restarts
|
|
23
|
+
stateDirectory: "/var/data", // optional: defaults to ~/.app-state
|
|
24
|
+
onSleep: (delayMs, info) => console.log(`Sleeping ${delayMs}ms`), // optional
|
|
21
25
|
});
|
|
22
26
|
|
|
23
|
-
await throttle.wait(); //
|
|
24
|
-
await throttle.wait(); //
|
|
27
|
+
await throttle.wait(); // waits if needed to stay within rate limit
|
|
28
|
+
await throttle.wait(5); // consume 5 units
|
|
25
29
|
```
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
When `persistKey` is set, throttle state is written to `{stateDirectory}/{persistKey}.json`. The default `stateDirectory` is `~/.app-state` (override with the `STATE_TRACKER_DIR` environment variable).
|
|
32
|
+
|
|
33
|
+
**Options:**
|
|
34
|
+
|
|
35
|
+
| Option | Type | Description |
|
|
36
|
+
|--------|------|-------------|
|
|
37
|
+
| `unitsPerSecond` | `number` | Rate limit (required) |
|
|
38
|
+
| `persistKey` | `string` | Key for disk persistence (optional) |
|
|
39
|
+
| `stateDirectory` | `string` | Directory for state files (optional) |
|
|
40
|
+
| `onSleep` | `function` | Callback when throttle sleeps (optional) |
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
### `getBackoffDelay(attempt: number, options?: BackoffOptions): number`
|
|
43
|
+
|
|
44
|
+
Calculate exponential backoff delay: `min(initialDelay * 2^attempt, maxDelay)`.
|
|
30
45
|
|
|
31
46
|
```typescript
|
|
32
|
-
import {
|
|
47
|
+
import { getBackoffDelay } from "@hardlydifficult/throttle";
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
getBackoffDelay(0); // 1000
|
|
50
|
+
getBackoffDelay(1); // 2000
|
|
51
|
+
getBackoffDelay(2); // 4000
|
|
52
|
+
getBackoffDelay(3, { initialDelayMs: 500, maxDelayMs: 10000 }); // 4000
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `sleep(ms: number): Promise<void>`
|
|
56
|
+
|
|
57
|
+
Sleep for the specified duration.
|
|
58
|
+
|
|
59
|
+
### `getRandomDelay(minMs: number, maxMs: number): number`
|
|
60
|
+
|
|
61
|
+
Get a randomized delay between `minMs` and `maxMs` (inclusive).
|
|
62
|
+
|
|
63
|
+
### `isConnectionError(error: unknown): boolean`
|
|
39
64
|
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
Check if an error is a connection error (e.g., ECONNREFUSED). Walks the error chain (`cause`, `errors`, `lastError`) to find connection indicators.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { isConnectionError } from "@hardlydifficult/throttle";
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await fetch("http://localhost:11434/api/generate");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (isConnectionError(err)) {
|
|
74
|
+
console.log("Service is not running");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `createThrottledUpdater(updateFn, intervalMs): ThrottledUpdater`
|
|
80
|
+
|
|
81
|
+
Batch rapid updates to avoid rate limits while ensuring the final state is always sent.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createThrottledUpdater } from "@hardlydifficult/throttle";
|
|
85
|
+
|
|
86
|
+
const updater = createThrottledUpdater(
|
|
87
|
+
(text) => message.edit(text),
|
|
88
|
+
2000
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
updater.update("Step 1...");
|
|
92
|
+
updater.update("Step 2...");
|
|
93
|
+
updater.update("Step 3..."); // only this one gets sent
|
|
94
|
+
|
|
95
|
+
await updater.flush(); // ensure final state is sent
|
|
96
|
+
updater.stop(); // clean up
|
|
42
97
|
```
|
|
98
|
+
|
|
99
|
+
| Method | Description |
|
|
100
|
+
|--------|-------------|
|
|
101
|
+
| `update(text)` | Queue an update with the latest text |
|
|
102
|
+
| `flush()` | Flush any pending update immediately |
|
|
103
|
+
| `stop()` | Stop the updater and clear pending timeouts |
|
package/dist/Throttle.d.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
export interface ThrottleSleepInfo {
|
|
2
|
+
weight: number;
|
|
3
|
+
limitPerSecond: number;
|
|
4
|
+
scheduledStart: number;
|
|
5
|
+
}
|
|
2
6
|
export interface ThrottleOptions {
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
unitsPerSecond: number;
|
|
8
|
+
persistKey?: string;
|
|
9
|
+
stateDirectory?: string;
|
|
10
|
+
onSleep?: (delayMs: number, info: ThrottleSleepInfo) => void;
|
|
5
11
|
}
|
|
6
12
|
export declare class Throttle {
|
|
7
|
-
private
|
|
8
|
-
private readonly
|
|
13
|
+
private nextAvailableAt;
|
|
14
|
+
private readonly unitsPerSecond;
|
|
15
|
+
private readonly stateTracker?;
|
|
9
16
|
private readonly onSleep?;
|
|
10
17
|
constructor(options: ThrottleOptions);
|
|
11
|
-
wait(): Promise<void>;
|
|
18
|
+
wait(weight?: number): Promise<void>;
|
|
12
19
|
}
|
|
13
20
|
//# sourceMappingURL=Throttle.d.ts.map
|
package/dist/Throttle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Throttle.d.ts","sourceRoot":"","sources":["../src/Throttle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Throttle.d.ts","sourceRoot":"","sources":["../src/Throttle.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC9D;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAqD;gBAElE,OAAO,EAAE,eAAe;IAoB9B,IAAI,CAAC,MAAM,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAuBtC"}
|
package/dist/Throttle.js
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Throttle = void 0;
|
|
4
|
-
const
|
|
4
|
+
const state_tracker_1 = require("@hardlydifficult/state-tracker");
|
|
5
5
|
const sleep = (ms) => new Promise((resolve) => {
|
|
6
6
|
setTimeout(resolve, ms);
|
|
7
7
|
});
|
|
8
8
|
class Throttle {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
nextAvailableAt;
|
|
10
|
+
unitsPerSecond;
|
|
11
|
+
stateTracker;
|
|
11
12
|
onSleep;
|
|
12
13
|
constructor(options) {
|
|
13
|
-
this.
|
|
14
|
-
if (this.minimumDelayMs <= 0) {
|
|
15
|
-
throw new Error("Throttle minimumDelay must be a positive duration");
|
|
16
|
-
}
|
|
14
|
+
this.unitsPerSecond = options.unitsPerSecond;
|
|
17
15
|
this.onSleep = options.onSleep;
|
|
16
|
+
if (!Number.isFinite(this.unitsPerSecond) || this.unitsPerSecond <= 0) {
|
|
17
|
+
throw new Error("Throttle requires a positive unitsPerSecond value");
|
|
18
|
+
}
|
|
19
|
+
if (options.persistKey !== undefined) {
|
|
20
|
+
this.stateTracker = new state_tracker_1.StateTracker({
|
|
21
|
+
key: options.persistKey,
|
|
22
|
+
default: Date.now(),
|
|
23
|
+
stateDirectory: options.stateDirectory,
|
|
24
|
+
});
|
|
25
|
+
this.nextAvailableAt = this.stateTracker.load();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.nextAvailableAt = 0;
|
|
29
|
+
}
|
|
18
30
|
}
|
|
19
|
-
async wait() {
|
|
31
|
+
async wait(weight = 1) {
|
|
32
|
+
if (!Number.isFinite(weight) || weight <= 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
20
35
|
const now = Date.now();
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
const startAt = Math.max(now, this.nextAvailableAt);
|
|
37
|
+
const processingWindowMs = (weight / this.unitsPerSecond) * 1000;
|
|
38
|
+
const delayMs = startAt - now;
|
|
39
|
+
const newNextAvailableAt = startAt + processingWindowMs;
|
|
40
|
+
this.stateTracker?.save(newNextAvailableAt);
|
|
41
|
+
this.nextAvailableAt = newNextAvailableAt;
|
|
42
|
+
if (delayMs > 0) {
|
|
43
|
+
this.onSleep?.(delayMs, {
|
|
44
|
+
weight,
|
|
45
|
+
limitPerSecond: this.unitsPerSecond,
|
|
46
|
+
scheduledStart: startAt,
|
|
47
|
+
});
|
|
48
|
+
await sleep(delayMs);
|
|
27
49
|
}
|
|
28
50
|
}
|
|
29
51
|
}
|
package/dist/Throttle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Throttle.js","sourceRoot":"","sources":["../src/Throttle.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"Throttle.js","sourceRoot":"","sources":["../src/Throttle.ts"],"names":[],"mappings":";;;AAAA,kEAA8D;AAE9D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAeL,MAAa,QAAQ;IACX,eAAe,CAAS;IACf,cAAc,CAAS;IACvB,YAAY,CAAwB;IACpC,OAAO,CAAsD;IAE9E,YAAY,OAAwB;QAClC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACrC,IAAI,CAAC,YAAY,GAAG,IAAI,4BAAY,CAAC;gBACnC,GAAG,EAAE,OAAO,CAAC,UAAU;gBACvB,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;gBACnB,cAAc,EAAE,OAAO,CAAC,cAAc;aACvC,CAAC,CAAC;YACH,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACpD,MAAM,kBAAkB,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;QACjE,MAAM,OAAO,GAAG,OAAO,GAAG,GAAG,CAAC;QAC9B,MAAM,kBAAkB,GAAG,OAAO,GAAG,kBAAkB,CAAC;QAExD,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC5C,IAAI,CAAC,eAAe,GAAG,kBAAkB,CAAC;QAE1C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE;gBACtB,MAAM;gBACN,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,cAAc,EAAE,OAAO;aACxB,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;CACF;AAjDD,4BAiDC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throttled message updater
|
|
3
|
+
*
|
|
4
|
+
* Batches rapid updates to avoid rate limits while ensuring
|
|
5
|
+
* the final state is always sent.
|
|
6
|
+
*/
|
|
7
|
+
export interface ThrottledUpdater {
|
|
8
|
+
/** Queue an update with the latest text */
|
|
9
|
+
update(text: string): void;
|
|
10
|
+
/** Flush any pending update immediately */
|
|
11
|
+
flush(): Promise<void>;
|
|
12
|
+
/** Stop the updater and clear any pending timeouts */
|
|
13
|
+
stop(): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a throttled message updater
|
|
17
|
+
*
|
|
18
|
+
* Limits how frequently the update function is called while ensuring
|
|
19
|
+
* the most recent content is always eventually sent.
|
|
20
|
+
*
|
|
21
|
+
* @param updateFn - Async function to call with updated text
|
|
22
|
+
* @param intervalMs - Minimum interval between updates
|
|
23
|
+
* @returns ThrottledUpdater instance
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const updater = createThrottledUpdater(
|
|
28
|
+
* (text) => message.edit(text),
|
|
29
|
+
* 2000
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* // These rapid updates will be batched
|
|
33
|
+
* updater.update('Step 1...');
|
|
34
|
+
* updater.update('Step 2...');
|
|
35
|
+
* updater.update('Step 3...');
|
|
36
|
+
*
|
|
37
|
+
* // Ensure final state is sent
|
|
38
|
+
* await updater.flush();
|
|
39
|
+
* updater.stop();
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function createThrottledUpdater(updateFn: (text: string) => Promise<void>, intervalMs: number): ThrottledUpdater;
|
|
43
|
+
//# sourceMappingURL=ThrottledUpdater.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThrottledUpdater.d.ts","sourceRoot":"","sources":["../src/ThrottledUpdater.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2CAA2C;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sDAAsD;IACtD,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EACzC,UAAU,EAAE,MAAM,GACjB,gBAAgB,CA0ElB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createThrottledUpdater = createThrottledUpdater;
|
|
4
|
+
/**
|
|
5
|
+
* Create a throttled message updater
|
|
6
|
+
*
|
|
7
|
+
* Limits how frequently the update function is called while ensuring
|
|
8
|
+
* the most recent content is always eventually sent.
|
|
9
|
+
*
|
|
10
|
+
* @param updateFn - Async function to call with updated text
|
|
11
|
+
* @param intervalMs - Minimum interval between updates
|
|
12
|
+
* @returns ThrottledUpdater instance
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const updater = createThrottledUpdater(
|
|
17
|
+
* (text) => message.edit(text),
|
|
18
|
+
* 2000
|
|
19
|
+
* );
|
|
20
|
+
*
|
|
21
|
+
* // These rapid updates will be batched
|
|
22
|
+
* updater.update('Step 1...');
|
|
23
|
+
* updater.update('Step 2...');
|
|
24
|
+
* updater.update('Step 3...');
|
|
25
|
+
*
|
|
26
|
+
* // Ensure final state is sent
|
|
27
|
+
* await updater.flush();
|
|
28
|
+
* updater.stop();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function createThrottledUpdater(updateFn, intervalMs) {
|
|
32
|
+
let lastUpdateTime = 0;
|
|
33
|
+
let pendingText = null;
|
|
34
|
+
let timeoutId = null;
|
|
35
|
+
let stopped = false;
|
|
36
|
+
const doUpdate = async (text) => {
|
|
37
|
+
if (stopped) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
lastUpdateTime = Date.now();
|
|
41
|
+
pendingText = null;
|
|
42
|
+
try {
|
|
43
|
+
await updateFn(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Errors are handled by the caller's updateFn
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const scheduleUpdate = () => {
|
|
50
|
+
if (timeoutId || stopped || pendingText === null) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const elapsed = Date.now() - lastUpdateTime;
|
|
54
|
+
const delay = Math.max(0, intervalMs - elapsed);
|
|
55
|
+
timeoutId = setTimeout(() => {
|
|
56
|
+
timeoutId = null;
|
|
57
|
+
if (pendingText !== null && !stopped) {
|
|
58
|
+
void doUpdate(pendingText);
|
|
59
|
+
}
|
|
60
|
+
}, delay);
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
update(text) {
|
|
64
|
+
if (stopped) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
if (now - lastUpdateTime >= intervalMs) {
|
|
69
|
+
// Enough time has passed, update immediately
|
|
70
|
+
void doUpdate(text);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Store for later and schedule if needed
|
|
74
|
+
pendingText = text;
|
|
75
|
+
scheduleUpdate();
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async flush() {
|
|
79
|
+
if (stopped) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (timeoutId) {
|
|
83
|
+
clearTimeout(timeoutId);
|
|
84
|
+
timeoutId = null;
|
|
85
|
+
}
|
|
86
|
+
if (pendingText !== null) {
|
|
87
|
+
await doUpdate(pendingText);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
stop() {
|
|
91
|
+
stopped = true;
|
|
92
|
+
if (timeoutId) {
|
|
93
|
+
clearTimeout(timeoutId);
|
|
94
|
+
timeoutId = null;
|
|
95
|
+
}
|
|
96
|
+
pendingText = null;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=ThrottledUpdater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThrottledUpdater.js","sourceRoot":"","sources":["../src/ThrottledUpdater.ts"],"names":[],"mappings":";;AA0CA,wDA6EC;AAxGD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAgB,sBAAsB,CACpC,QAAyC,EACzC,UAAkB;IAElB,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,IAAI,SAAS,GAAyC,IAAI,CAAC;IAC3D,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;QACrD,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;QACT,CAAC;QACD,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,WAAW,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,GAAS,EAAE;QAChC,IAAI,SAAS,IAAI,OAAO,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;YACjD,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;QAEhD,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,SAAS,GAAG,IAAI,CAAC;YACjB,IAAI,WAAW,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACrC,KAAK,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,CAAC;IAEF,OAAO;QACL,MAAM,CAAC,IAAY;YACjB,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO;YACT,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,IAAI,GAAG,GAAG,cAAc,IAAI,UAAU,EAAE,CAAC;gBACvC,6CAA6C;gBAC7C,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,yCAAyC;gBACzC,WAAW,GAAG,IAAI,CAAC;gBACnB,cAAc,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;QAED,KAAK,CAAC,KAAK;YACT,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,IAAI,SAAS,EAAE,CAAC;gBACd,YAAY,CAAC,SAAS,CAAC,CAAC;gBACxB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;gBACzB,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI;YACF,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,SAAS,EAAE,CAAC;gBACd,YAAY,CAAC,SAAS,CAAC,CAAC;gBACxB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exponential backoff utilities for retry logic
|
|
3
|
+
*/
|
|
4
|
+
export interface BackoffOptions {
|
|
5
|
+
/** Initial delay in milliseconds (default: 1000) */
|
|
6
|
+
initialDelayMs?: number;
|
|
7
|
+
/** Maximum delay in milliseconds (default: 60000) */
|
|
8
|
+
maxDelayMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Calculate exponential backoff delay for a given attempt number
|
|
12
|
+
* Uses formula: min(initialDelay * 2^attempt, maxDelay)
|
|
13
|
+
*
|
|
14
|
+
* @param attempt - The attempt number (0-indexed)
|
|
15
|
+
* @param options - Configuration options
|
|
16
|
+
* @returns Delay in milliseconds
|
|
17
|
+
*/
|
|
18
|
+
export declare function getBackoffDelay(attempt: number, options?: BackoffOptions): number;
|
|
19
|
+
/**
|
|
20
|
+
* Sleep for the specified duration
|
|
21
|
+
*/
|
|
22
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get a randomized delay for retry logic
|
|
25
|
+
* Returns a delay between minMs and maxMs (inclusive)
|
|
26
|
+
*/
|
|
27
|
+
export declare function getRandomDelay(minMs: number, maxMs: number): number;
|
|
28
|
+
//# sourceMappingURL=backoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
package/dist/backoff.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Exponential backoff utilities for retry logic
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getBackoffDelay = getBackoffDelay;
|
|
7
|
+
exports.sleep = sleep;
|
|
8
|
+
exports.getRandomDelay = getRandomDelay;
|
|
9
|
+
const DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
10
|
+
const DEFAULT_MAX_DELAY_MS = 60000;
|
|
11
|
+
/**
|
|
12
|
+
* Calculate exponential backoff delay for a given attempt number
|
|
13
|
+
* Uses formula: min(initialDelay * 2^attempt, maxDelay)
|
|
14
|
+
*
|
|
15
|
+
* @param attempt - The attempt number (0-indexed)
|
|
16
|
+
* @param options - Configuration options
|
|
17
|
+
* @returns Delay in milliseconds
|
|
18
|
+
*/
|
|
19
|
+
function getBackoffDelay(attempt, options = {}) {
|
|
20
|
+
const initialDelay = options.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
21
|
+
const maxDelay = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
22
|
+
const delay = initialDelay * Math.pow(2, attempt);
|
|
23
|
+
return Math.min(delay, maxDelay);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sleep for the specified duration
|
|
27
|
+
*/
|
|
28
|
+
function sleep(ms) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
setTimeout(resolve, ms);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get a randomized delay for retry logic
|
|
35
|
+
* Returns a delay between minMs and maxMs (inclusive)
|
|
36
|
+
*/
|
|
37
|
+
function getRandomDelay(minMs, maxMs) {
|
|
38
|
+
return Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAoBH,0CAQC;AAKD,sBAIC;AAMD,wCAEC;AApCD,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC;;;;;;;GAOG;AACH,SAAgB,eAAe,CAC7B,OAAe,EACf,UAA0B,EAAE;IAE5B,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;IACxE,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,IAAI,oBAAoB,CAAC;IAC5D,MAAM,KAAK,GAAG,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAgB,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAAC,KAAa,EAAE,KAAa;IACzD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AACjE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export { Throttle, type ThrottleOptions } from "./Throttle";
|
|
2
|
-
export {
|
|
1
|
+
export { Throttle, type ThrottleOptions, type ThrottleSleepInfo, } from "./Throttle";
|
|
2
|
+
export { getBackoffDelay, sleep, getRandomDelay, type BackoffOptions, } from "./backoff";
|
|
3
|
+
export { isConnectionError } from "./isConnectionError";
|
|
4
|
+
export { createThrottledUpdater, type ThrottledUpdater, } from "./ThrottledUpdater";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,KAAK,EACL,cAAc,EACd,KAAK,cAAc,GACpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EACL,sBAAsB,EACtB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.createThrottledUpdater = exports.isConnectionError = exports.getRandomDelay = exports.sleep = exports.getBackoffDelay = exports.Throttle = void 0;
|
|
4
4
|
var Throttle_1 = require("./Throttle");
|
|
5
5
|
Object.defineProperty(exports, "Throttle", { enumerable: true, get: function () { return Throttle_1.Throttle; } });
|
|
6
|
-
var
|
|
7
|
-
Object.defineProperty(exports, "
|
|
6
|
+
var backoff_1 = require("./backoff");
|
|
7
|
+
Object.defineProperty(exports, "getBackoffDelay", { enumerable: true, get: function () { return backoff_1.getBackoffDelay; } });
|
|
8
|
+
Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return backoff_1.sleep; } });
|
|
9
|
+
Object.defineProperty(exports, "getRandomDelay", { enumerable: true, get: function () { return backoff_1.getRandomDelay; } });
|
|
10
|
+
var isConnectionError_1 = require("./isConnectionError");
|
|
11
|
+
Object.defineProperty(exports, "isConnectionError", { enumerable: true, get: function () { return isConnectionError_1.isConnectionError; } });
|
|
12
|
+
var ThrottledUpdater_1 = require("./ThrottledUpdater");
|
|
13
|
+
Object.defineProperty(exports, "createThrottledUpdater", { enumerable: true, get: function () { return ThrottledUpdater_1.createThrottledUpdater; } });
|
|
8
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAIoB;AAHlB,oGAAA,QAAQ,OAAA;AAKV,qCAKmB;AAJjB,0GAAA,eAAe,OAAA;AACf,gGAAA,KAAK,OAAA;AACL,yGAAA,cAAc,OAAA;AAIhB,yDAAwD;AAA/C,sHAAA,iBAAiB,OAAA;AAE1B,uDAG4B;AAF1B,0HAAA,sBAAsB,OAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection error detection utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Check if an error is a connection error (e.g., ECONNREFUSED when Ollama is not running).
|
|
6
|
+
* Walks the error chain (cause, errors, lastError) to find connection indicators.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isConnectionError(error: unknown): boolean;
|
|
9
|
+
//# sourceMappingURL=isConnectionError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isConnectionError.d.ts","sourceRoot":"","sources":["../src/isConnectionError.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA4CzD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Connection error detection utilities
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isConnectionError = isConnectionError;
|
|
7
|
+
/**
|
|
8
|
+
* Check if an error is a connection error (e.g., ECONNREFUSED when Ollama is not running).
|
|
9
|
+
* Walks the error chain (cause, errors, lastError) to find connection indicators.
|
|
10
|
+
*/
|
|
11
|
+
function isConnectionError(error) {
|
|
12
|
+
if (!(error instanceof Error)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const message = error.message.toLowerCase();
|
|
16
|
+
// Check the top-level message
|
|
17
|
+
if (message.includes("econnrefused") ||
|
|
18
|
+
message.includes("cannot connect to api")) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
// Check nested error properties from AI SDK RetryError / APICallError
|
|
22
|
+
const err = error;
|
|
23
|
+
if (err.lastError instanceof Error) {
|
|
24
|
+
if (isConnectionError(err.lastError)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (err.cause instanceof Error) {
|
|
29
|
+
if (isConnectionError(err.cause)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(err.errors)) {
|
|
34
|
+
for (const nested of err.errors) {
|
|
35
|
+
if (isConnectionError(nested)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Check for Node.js error code
|
|
41
|
+
if (err.code === "ECONNREFUSED") {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=isConnectionError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isConnectionError.js","sourceRoot":"","sources":["../src/isConnectionError.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAMH,8CA4CC;AAhDD;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAE5C,8BAA8B;IAC9B,IACE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAChC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EACzC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sEAAsE;IACtE,MAAM,GAAG,GAAG,KAA2C,CAAC;IAExD,IAAI,GAAG,CAAC,SAAS,YAAY,KAAK,EAAE,CAAC;QACnC,IAAI,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,GAAG,CAAC,KAAK,YAAY,KAAK,EAAE,CAAC;QAC/B,IAAI,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YAChC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hardlydifficult/throttle",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
"clean": "rm -rf dist"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@hardlydifficult/state-tracker": "
|
|
19
|
-
"@hardlydifficult/date-time": "1.0.0"
|
|
18
|
+
"@hardlydifficult/state-tracker": "2.0.0"
|
|
20
19
|
},
|
|
21
20
|
"devDependencies": {
|
|
22
21
|
"@types/node": "20.19.31",
|
|
@@ -24,8 +23,7 @@
|
|
|
24
23
|
"vitest": "1.6.1"
|
|
25
24
|
},
|
|
26
25
|
"peerDependencies": {
|
|
27
|
-
"@hardlydifficult/state-tracker": "
|
|
28
|
-
"@hardlydifficult/date-time": "1.0.0"
|
|
26
|
+
"@hardlydifficult/state-tracker": "2.0.0"
|
|
29
27
|
},
|
|
30
28
|
"engines": {
|
|
31
29
|
"node": ">=18.0.0"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface WeightedThrottleSleepInfo {
|
|
2
|
-
weight: number;
|
|
3
|
-
limitPerSecond: number;
|
|
4
|
-
scheduledStart: number;
|
|
5
|
-
}
|
|
6
|
-
export interface WeightedThrottleOptions {
|
|
7
|
-
unitsPerSecond: number;
|
|
8
|
-
persistKey?: string;
|
|
9
|
-
stateDirectory?: string;
|
|
10
|
-
onSleep?: (delayMs: number, info: WeightedThrottleSleepInfo) => void;
|
|
11
|
-
}
|
|
12
|
-
export declare class WeightedThrottle {
|
|
13
|
-
private nextAvailableAt;
|
|
14
|
-
private readonly unitsPerSecond;
|
|
15
|
-
private readonly stateTracker?;
|
|
16
|
-
private readonly onSleep?;
|
|
17
|
-
constructor(options: WeightedThrottleOptions);
|
|
18
|
-
wait(weight: number): Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=WeightedThrottle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WeightedThrottle.d.ts","sourceRoot":"","sources":["../src/WeightedThrottle.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACtE;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAGf;gBAEE,OAAO,EAAE,uBAAuB;IAsBtC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuB1C"}
|
package/dist/WeightedThrottle.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WeightedThrottle = void 0;
|
|
4
|
-
const state_tracker_1 = require("@hardlydifficult/state-tracker");
|
|
5
|
-
const sleep = (ms) => new Promise((resolve) => {
|
|
6
|
-
setTimeout(resolve, ms);
|
|
7
|
-
});
|
|
8
|
-
class WeightedThrottle {
|
|
9
|
-
nextAvailableAt;
|
|
10
|
-
unitsPerSecond;
|
|
11
|
-
stateTracker;
|
|
12
|
-
onSleep;
|
|
13
|
-
constructor(options) {
|
|
14
|
-
this.unitsPerSecond = options.unitsPerSecond;
|
|
15
|
-
this.onSleep = options.onSleep;
|
|
16
|
-
if (!Number.isFinite(this.unitsPerSecond) || this.unitsPerSecond <= 0) {
|
|
17
|
-
throw new Error("WeightedThrottle requires a positive unitsPerSecond value");
|
|
18
|
-
}
|
|
19
|
-
if (options.persistKey !== undefined) {
|
|
20
|
-
this.stateTracker = new state_tracker_1.StateTracker({
|
|
21
|
-
key: options.persistKey,
|
|
22
|
-
default: Date.now(),
|
|
23
|
-
stateDirectory: options.stateDirectory,
|
|
24
|
-
});
|
|
25
|
-
this.nextAvailableAt = this.stateTracker.load();
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
this.nextAvailableAt = 0;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async wait(weight) {
|
|
32
|
-
if (!Number.isFinite(weight) || weight <= 0) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
const startAt = Math.max(now, this.nextAvailableAt);
|
|
37
|
-
const processingWindowMs = (weight / this.unitsPerSecond) * 1000;
|
|
38
|
-
const delayMs = startAt - now;
|
|
39
|
-
const newNextAvailableAt = startAt + processingWindowMs;
|
|
40
|
-
this.stateTracker?.save(newNextAvailableAt);
|
|
41
|
-
this.nextAvailableAt = newNextAvailableAt;
|
|
42
|
-
if (delayMs > 0) {
|
|
43
|
-
this.onSleep?.(delayMs, {
|
|
44
|
-
weight,
|
|
45
|
-
limitPerSecond: this.unitsPerSecond,
|
|
46
|
-
scheduledStart: startAt,
|
|
47
|
-
});
|
|
48
|
-
await sleep(delayMs);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
exports.WeightedThrottle = WeightedThrottle;
|
|
53
|
-
//# sourceMappingURL=WeightedThrottle.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WeightedThrottle.js","sourceRoot":"","sources":["../src/WeightedThrottle.ts"],"names":[],"mappings":";;;AAAA,kEAA8D;AAE9D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAeL,MAAa,gBAAgB;IACnB,eAAe,CAAS;IACf,cAAc,CAAS;IACvB,YAAY,CAAwB;IACpC,OAAO,CAGd;IAEV,YAAY,OAAgC;QAC1C,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;QACJ,CAAC;QAED,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACrC,IAAI,CAAC,YAAY,GAAG,IAAI,4BAAY,CAAC;gBACnC,GAAG,EAAE,OAAO,CAAC,UAAU;gBACvB,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;gBACnB,cAAc,EAAE,OAAO,CAAC,cAAc;aACvC,CAAC,CAAC;YACH,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc;QACvB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACpD,MAAM,kBAAkB,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,IAAI,CAAC;QACjE,MAAM,OAAO,GAAG,OAAO,GAAG,GAAG,CAAC;QAC9B,MAAM,kBAAkB,GAAG,OAAO,GAAG,kBAAkB,CAAC;QAExD,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC5C,IAAI,CAAC,eAAe,GAAG,kBAAkB,CAAC;QAE1C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE;gBACtB,MAAM;gBACN,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,cAAc,EAAE,OAAO;aACxB,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;CACF;AAtDD,4CAsDC"}
|