@yaebal/throttle 0.0.1 → 0.0.2

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
  # @yaebal/throttle
2
2
 
3
- minimum ms between outgoing calls. defaults to 34 (~30/sec, telegram's global cap).
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
- * space out outgoing api calls by at least `minIntervalMs` (hooks `api.before`).
16
- * calls past the cap are delayed, not dropped, so nothing is lost — they just
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
@@ -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;AAExC,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;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,GAAE,eAAoB,GAAG,IAAI,CAgBtE"}
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
- export function throttle(api, options = {}) {
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;;;;GAIG;AACH,MAAM,UAAU,QAAQ,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"}
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
@@ -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;AAC7B,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,IAAyE,CAAC;IAE9E,MAAM,GAAG,GAAG;QACX,MAAM,EAAE,CAAC,CAAc,EAAE,EAAE;YAC1B,IAAI,GAAG,CAAC,CAAC;QACV,CAAC;KACQ,CAAC;IAEX,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"}
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.1",
3
+ "version": "0.0.2",
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.1"
19
+ "@yaebal/core": "0.0.3"
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: ((method: string, params: unknown) => Promise<unknown>) | undefined;
20
+ let hook: BeforeHook | undefined;
20
21
 
21
22
  const api = {
22
- before: (h: typeof hook) => {
23
+ before: (h: BeforeHook) => {
23
24
  hook = h;
25
+ return api as Api;
24
26
  },
25
- } as never;
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
- export function throttle(api: Api, options: ThrottleOptions = {}): void {
27
+ function installThrottle(api: Api, options: ThrottleOptions = {}): void {
24
28
  const interval = options.minIntervalMs ?? 34;
25
29
 
26
30
  let next = 0;
@@ -37,3 +41,21 @@ export function throttle(api: Api, options: ThrottleOptions = {}): void {
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
+ }