@hardlydifficult/throttle 2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hardlydifficult/throttle
2
2
 
3
- Rate limiting with optional weight and state persistence.
3
+ Rate limiting, exponential backoff, connection error detection, and throttled message updates.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,21 +8,96 @@ Rate limiting with optional weight and state persistence.
8
8
  npm install @hardlydifficult/throttle
9
9
  ```
10
10
 
11
- ## Throttle
11
+ ## API
12
+
13
+ ### `Throttle`
14
+
15
+ Token-bucket rate limiter with optional persistent state.
12
16
 
13
17
  ```typescript
14
- import { Throttle } from '@hardlydifficult/throttle';
18
+ import { Throttle } from "@hardlydifficult/throttle";
15
19
 
16
20
  const throttle = new Throttle({
17
21
  unitsPerSecond: 10,
18
- persistKey: 'api-throttle', // optional: survives restarts
19
- stateDirectory: './my-state', // optional: defaults to ~/.app-state
20
- onSleep: (ms, info) => console.log(`Sleeping ${ms}ms (weight: ${info.weight})`),
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(); // consumes 1 unit (default)
24
- await throttle.wait(5); // consumes 5 units
25
- await throttle.wait(10); // consumes 10 units, sleeps if needed
27
+ await throttle.wait(); // waits if needed to stay within rate limit
28
+ await throttle.wait(5); // consume 5 units
26
29
  ```
27
30
 
28
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) |
41
+
42
+ ### `getBackoffDelay(attempt: number, options?: BackoffOptions): number`
43
+
44
+ Calculate exponential backoff delay: `min(initialDelay * 2^attempt, maxDelay)`.
45
+
46
+ ```typescript
47
+ import { getBackoffDelay } from "@hardlydifficult/throttle";
48
+
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`
64
+
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
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 |
@@ -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"}
@@ -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,2 +1,5 @@
1
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";
2
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
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,6 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Throttle = void 0;
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 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; } });
6
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,uCAIoB;AAHlB,oGAAA,QAAQ,OAAA"}
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": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -15,7 +15,7 @@
15
15
  "clean": "rm -rf dist"
16
16
  },
17
17
  "dependencies": {
18
- "@hardlydifficult/state-tracker": "file:../state-tracker"
18
+ "@hardlydifficult/state-tracker": "2.0.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "20.19.31",
@@ -23,7 +23,7 @@
23
23
  "vitest": "1.6.1"
24
24
  },
25
25
  "peerDependencies": {
26
- "@hardlydifficult/state-tracker": ">=1.0.0"
26
+ "@hardlydifficult/state-tracker": "2.0.0"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=18.0.0"