@yaebal/throttle 0.0.1 → 0.0.3
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 +15 -1
- package/lib/index.d.ts +4 -6
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +13 -1
- package/lib/index.js.map +1 -1
- package/lib/index.test.js +12 -0
- package/lib/index.test.js.map +1 -1
- package/package.json +2 -2
- package/src/index.test.ts +20 -4
- package/src/index.ts +25 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @yaebal/throttle
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
space out outgoing API calls. defaults to 34 ms between calls (~30/sec, telegram's global cap).
|
|
4
4
|
|
|
5
5
|
## install
|
|
6
6
|
|
|
@@ -8,6 +8,20 @@ minimum ms between outgoing calls. defaults to 34 (~30/sec, telegram's global ca
|
|
|
8
8
|
pnpm add @yaebal/throttle
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { throttle } from "@yaebal/throttle";
|
|
15
|
+
|
|
16
|
+
bot.install(throttle({ minIntervalMs: 100 }));
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
the direct api-hook form is still available:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
throttle(bot.api, { minIntervalMs: 100 });
|
|
23
|
+
```
|
|
24
|
+
|
|
11
25
|
---
|
|
12
26
|
|
|
13
27
|
part of [**yaebal**](https://github.com/neverlane/yaebal) — a type-safe, runtime-agnostic Telegram Bot API framework. MIT.
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Api } from "@yaebal/core";
|
|
1
|
+
import type { Api, BotPlugin } from "@yaebal/core";
|
|
2
2
|
export interface ThrottleOptions {
|
|
3
3
|
/** minimum ms between outgoing calls. defaults to 34 (~30/sec, telegram's global cap). */
|
|
4
4
|
minIntervalMs?: number;
|
|
@@ -11,10 +11,8 @@ export declare function reserve(now: number, next: number, interval: number): {
|
|
|
11
11
|
at: number;
|
|
12
12
|
next: number;
|
|
13
13
|
};
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* queue up behind the rate limit.
|
|
18
|
-
*/
|
|
14
|
+
/** create an installable bot plugin: `bot.install(throttle())`. */
|
|
15
|
+
export declare function throttle(options?: ThrottleOptions): BotPlugin;
|
|
16
|
+
/** install throttling on a bot's API directly: `throttle(bot.api)`. */
|
|
19
17
|
export declare function throttle(api: Api, options?: ThrottleOptions): void;
|
|
20
18
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC/B,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAIjG;AA6BD,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;AAC/D,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -6,12 +6,15 @@ export function reserve(now, next, interval) {
|
|
|
6
6
|
const at = Math.max(now, next);
|
|
7
7
|
return { at, next: at + interval };
|
|
8
8
|
}
|
|
9
|
+
function isApi(value) {
|
|
10
|
+
return typeof value?.before === "function";
|
|
11
|
+
}
|
|
9
12
|
/**
|
|
10
13
|
* space out outgoing api calls by at least `minIntervalMs` (hooks `api.before`).
|
|
11
14
|
* calls past the cap are delayed, not dropped, so nothing is lost — they just
|
|
12
15
|
* queue up behind the rate limit.
|
|
13
16
|
*/
|
|
14
|
-
|
|
17
|
+
function installThrottle(api, options = {}) {
|
|
15
18
|
const interval = options.minIntervalMs ?? 34;
|
|
16
19
|
let next = 0;
|
|
17
20
|
api.before(async () => {
|
|
@@ -24,4 +27,13 @@ export function throttle(api, options = {}) {
|
|
|
24
27
|
return undefined; // keep params unchanged
|
|
25
28
|
});
|
|
26
29
|
}
|
|
30
|
+
export function throttle(apiOrOptions, options = {}) {
|
|
31
|
+
if (isApi(apiOrOptions))
|
|
32
|
+
return installThrottle(apiOrOptions, options);
|
|
33
|
+
const pluginOptions = apiOrOptions ?? {};
|
|
34
|
+
return (bot) => {
|
|
35
|
+
installThrottle(bot.api, pluginOptions);
|
|
36
|
+
return bot;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
27
39
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,IAAY,EAAE,QAAgB;IAClE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAE/B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,QAAQ,EAAE,CAAC;AACpC,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,IAAY,EAAE,QAAgB;IAClE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAE/B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,QAAQ,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,KAAK,CAAC,KAAwC;IACtD,OAAO,OAAQ,KAAyB,EAAE,MAAM,KAAK,UAAU,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,GAAQ,EAAE,UAA2B,EAAE;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IAE7C,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,GAAG,CAAC,MAAM,CAAC,KAAK,IAAwB,EAAE;QACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAE1C,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAEjB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC;QAC3B,IAAI,IAAI,GAAG,CAAC;YAAE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAE5D,OAAO,SAAS,CAAC,CAAC,wBAAwB;IAC3C,CAAC,CAAC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,QAAQ,CACvB,YAAoC,EACpC,UAA2B,EAAE;IAE7B,IAAI,KAAK,CAAC,YAAY,CAAC;QAAE,OAAO,eAAe,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAEvE,MAAM,aAAa,GAAG,YAAY,IAAI,EAAE,CAAC;IAEzC,OAAO,CAAC,GAAG,EAAE,EAAE;QACd,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACxC,OAAO,GAAG,CAAC;IACZ,CAAC,CAAC;AACH,CAAC"}
|
package/lib/index.test.js
CHANGED
|
@@ -16,10 +16,22 @@ test("throttle registers a before hook that resolves", async () => {
|
|
|
16
16
|
const api = {
|
|
17
17
|
before: (h) => {
|
|
18
18
|
hook = h;
|
|
19
|
+
return api;
|
|
19
20
|
},
|
|
20
21
|
};
|
|
21
22
|
throttle(api, { minIntervalMs: 0 });
|
|
22
23
|
assert.equal(typeof hook, "function");
|
|
23
24
|
await hook?.("sendMessage", undefined); // interval 0 → no delay
|
|
24
25
|
});
|
|
26
|
+
test("throttle can be installed as a bot plugin", () => {
|
|
27
|
+
let hook;
|
|
28
|
+
const api = {
|
|
29
|
+
before: (h) => {
|
|
30
|
+
hook = h;
|
|
31
|
+
return api;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
throttle({ minIntervalMs: 0 })({ api });
|
|
35
|
+
assert.equal(typeof hook, "function");
|
|
36
|
+
});
|
|
25
37
|
//# sourceMappingURL=index.test.js.map
|
package/lib/index.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE/C,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACpD,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACvD,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;IACpE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;IACjE,IAAI,IAA4B,CAAC;IAEjC,MAAM,GAAG,GAAG;QACX,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE;YACzB,IAAI,GAAG,CAAC,CAAC;YACT,OAAO,GAAU,CAAC;QACnB,CAAC;KACM,CAAC;IAET,QAAQ,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;IACpC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,UAAU,CAAC,CAAC;IAEtC,MAAM,IAAI,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,wBAAwB;AACjE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACtD,IAAI,IAA4B,CAAC;IACjC,MAAM,GAAG,GAAG;QACX,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE;YACzB,IAAI,GAAG,CAAC,CAAC;YACT,OAAO,GAAU,CAAC;QACnB,CAAC;KACM,CAAC;IAET,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAW,CAAC,CAAC;IAEjD,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,UAAU,CAAC,CAAC;AACvC,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaebal/throttle",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "yaebal throttle plugin — space out outgoing API calls to avoid 429s.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@yaebal/core": "0.0.
|
|
19
|
+
"@yaebal/core": "0.0.5"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "latest"
|
package/src/index.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import type { Api, BeforeHook } from "@yaebal/core";
|
|
3
4
|
import { reserve, throttle } from "./index.js";
|
|
4
5
|
|
|
5
6
|
test("reserve runs the first call immediately", () => {
|
|
@@ -16,16 +17,31 @@ test("reserve respects real elapsed time (no artificial wait)", () => {
|
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
test("throttle registers a before hook that resolves", async () => {
|
|
19
|
-
let hook:
|
|
20
|
+
let hook: BeforeHook | undefined;
|
|
20
21
|
|
|
21
22
|
const api = {
|
|
22
|
-
before: (h:
|
|
23
|
+
before: (h: BeforeHook) => {
|
|
23
24
|
hook = h;
|
|
25
|
+
return api as Api;
|
|
24
26
|
},
|
|
25
|
-
} as
|
|
27
|
+
} as Api;
|
|
26
28
|
|
|
27
29
|
throttle(api, { minIntervalMs: 0 });
|
|
28
30
|
assert.equal(typeof hook, "function");
|
|
29
|
-
|
|
31
|
+
|
|
30
32
|
await hook?.("sendMessage", undefined); // interval 0 → no delay
|
|
31
33
|
});
|
|
34
|
+
|
|
35
|
+
test("throttle can be installed as a bot plugin", () => {
|
|
36
|
+
let hook: BeforeHook | undefined;
|
|
37
|
+
const api = {
|
|
38
|
+
before: (h: BeforeHook) => {
|
|
39
|
+
hook = h;
|
|
40
|
+
return api as Api;
|
|
41
|
+
},
|
|
42
|
+
} as Api;
|
|
43
|
+
|
|
44
|
+
throttle({ minIntervalMs: 0 })({ api } as never);
|
|
45
|
+
|
|
46
|
+
assert.equal(typeof hook, "function");
|
|
47
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Api } from "@yaebal/core";
|
|
1
|
+
import type { Api, BotPlugin } from "@yaebal/core";
|
|
2
2
|
|
|
3
3
|
export interface ThrottleOptions {
|
|
4
4
|
/** minimum ms between outgoing calls. defaults to 34 (~30/sec, telegram's global cap). */
|
|
@@ -15,12 +15,16 @@ export function reserve(now: number, next: number, interval: number): { at: numb
|
|
|
15
15
|
return { at, next: at + interval };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function isApi(value: Api | ThrottleOptions | undefined): value is Api {
|
|
19
|
+
return typeof (value as Api | undefined)?.before === "function";
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
/**
|
|
19
23
|
* space out outgoing api calls by at least `minIntervalMs` (hooks `api.before`).
|
|
20
24
|
* calls past the cap are delayed, not dropped, so nothing is lost — they just
|
|
21
25
|
* queue up behind the rate limit.
|
|
22
26
|
*/
|
|
23
|
-
|
|
27
|
+
function installThrottle(api: Api, options: ThrottleOptions = {}): void {
|
|
24
28
|
const interval = options.minIntervalMs ?? 34;
|
|
25
29
|
|
|
26
30
|
let next = 0;
|
|
@@ -33,7 +37,25 @@ export function throttle(api: Api, options: ThrottleOptions = {}): void {
|
|
|
33
37
|
|
|
34
38
|
const wait = slot.at - now;
|
|
35
39
|
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
36
|
-
|
|
40
|
+
|
|
37
41
|
return undefined; // keep params unchanged
|
|
38
42
|
});
|
|
39
43
|
}
|
|
44
|
+
|
|
45
|
+
/** create an installable bot plugin: `bot.install(throttle())`. */
|
|
46
|
+
export function throttle(options?: ThrottleOptions): BotPlugin;
|
|
47
|
+
/** install throttling on a bot's API directly: `throttle(bot.api)`. */
|
|
48
|
+
export function throttle(api: Api, options?: ThrottleOptions): void;
|
|
49
|
+
export function throttle(
|
|
50
|
+
apiOrOptions?: Api | ThrottleOptions,
|
|
51
|
+
options: ThrottleOptions = {},
|
|
52
|
+
): BotPlugin | void {
|
|
53
|
+
if (isApi(apiOrOptions)) return installThrottle(apiOrOptions, options);
|
|
54
|
+
|
|
55
|
+
const pluginOptions = apiOrOptions ?? {};
|
|
56
|
+
|
|
57
|
+
return (bot) => {
|
|
58
|
+
installThrottle(bot.api, pluginOptions);
|
|
59
|
+
return bot;
|
|
60
|
+
};
|
|
61
|
+
}
|