@xbibzlibrary/telebibz 0.2.1 → 0.3.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/docs/API.md CHANGED
@@ -73,6 +73,7 @@ type BotStatus =
73
73
  | `polling.allowedUpdates` | `string[]` | `[]` | Telegram update filters. |
74
74
  | `polling.retryDelayMs` | `number` | `500` | Initial delay when polling fails. |
75
75
  | `polling.maxRetryDelayMs` | `number` | `30000` | Maximum reconnect delay. |
76
+ | `updates.concurrency` | `number` | `Infinity` | Cap on how many updates are processed at the same time. Updates always run in parallel across chats and stay ordered within a single chat, so bursts of 1000+ messages are handled at once. |
76
77
 
77
78
  ### Constructor `Bot`
78
79
 
@@ -218,7 +219,7 @@ launch(options?: {
218
219
  }): Promise<void>
219
220
  ```
220
221
 
221
- Runs the bot in polling mode. On start, the lifecycle moves through `starting` to `running`, then the `getUpdates()` loop processes each update sequentially. Polling failures emit `polling:reconnect` and use exponential backoff.
222
+ Runs the bot in polling mode. On start, the lifecycle moves through `starting` to `running`, then the `getUpdates()` loop processes each batch of updates concurrently: updates for different chats run in parallel while updates for the same chat keep their arrival order. Polling failures emit `polling:reconnect` and use exponential backoff.
222
223
 
223
224
  Modes other than `"polling"` throw an error and suggest using `createWebhookHandler()` for webhooks.
224
225
 
@@ -295,8 +296,54 @@ handleUpdate(update: Update): Promise<void>
295
296
 
296
297
  Processes a single update manually. The method determines the session key from `chat.id` and `from.id`, creates a `Context`, emits `update` and `message` events, runs middleware then the router, and saves the session after the pipeline completes.
297
298
 
299
+ Updates for different chats are processed in parallel; updates for the same chat are serialized in arrival order, so sessions, wizards, and conversations never interleave and session writes are never lost. A burst of concurrent updates triggers exactly one `getMe` initialization.
300
+
298
301
  Pipeline errors set the bot status to `error`, emit `bot:error`, and then rethrow the error.
299
302
 
303
+ ### `bot.handleUpdates(updates)`
304
+
305
+ ```ts
306
+ handleUpdates(updates: readonly Update[]): Promise<void>
307
+ ```
308
+
309
+ Handles a whole batch of updates at once: every chat in the batch is processed immediately — parallel across chats, ordered per chat — so a burst of 1000 messages is never stuck behind one slow handler. Individual handler failures are logged, emitted as `update:error`, and passed to the `catch()` error boundary; they never reject this promise. The polling loop uses this method for every `getUpdates` batch.
310
+
311
+ ### `bot.broadcast(chatIds, send, options?)`
312
+
313
+ ```ts
314
+ broadcast(
315
+ chatIds: readonly ChatId[],
316
+ send: (chatId: ChatId) => Promise<unknown>,
317
+ options?: BroadcastOptions,
318
+ ): Promise<BroadcastReport>
319
+ ```
320
+
321
+ Sends to many chats in parallel — built for broadcasts to 1000+ users. There is no proactive cooldown: every chat is attempted at once (up to `options.concurrency`, default `Infinity`). When Telegram answers 429, the send is retried automatically after exactly the `retry_after` delay Telegram ordered (up to `options.maxAttempts`, default `10`), so bursts deliver completely instead of failing. Non-retryable errors (for example, a chat the bot cannot message) are recorded per chat in the returned report.
322
+
323
+ ```ts
324
+ const report = await bot.broadcast(
325
+ subscriberIds,
326
+ (chatId) => bot.api.methods.sendMessage({ chat_id: chatId, text: "Newsletter #42" }),
327
+ { onProgress: (progress) => console.log(`${progress.delivered}/${progress.total} delivered`) },
328
+ );
329
+ console.log(`Delivered ${report.delivered} of ${report.total} in ${report.durationMs}ms`);
330
+ for (const failure of report.failures) console.warn(`Failed: ${failure.chatId} — ${failure.error}`);
331
+ ```
332
+
333
+ #### `BroadcastOptions` and `BroadcastReport`
334
+
335
+ | Property | Type | Default | Description |
336
+ |---|---|---:|---|
337
+ | `BroadcastOptions.concurrency` | `number` | `Infinity` | How many chats are messaged at the same time. |
338
+ | `BroadcastOptions.maxAttempts` | `number` | `10` | Attempts per chat when Telegram answers 429. |
339
+ | `BroadcastOptions.onProgress` | `(progress: BroadcastProgress) => void` | — | Called after each chat settles. |
340
+ | `BroadcastOptions.signal` | `AbortSignal` | — | Aborts pending sends; delivered messages stay delivered. |
341
+ | `BroadcastReport.total` | `number` | — | Chats in the run. |
342
+ | `BroadcastReport.delivered` | `number` | — | Chats that received the message. |
343
+ | `BroadcastReport.failed` | `number` | — | Chats that did not. |
344
+ | `BroadcastReport.durationMs` | `number` | — | Wall-clock duration of the run. |
345
+ | `BroadcastReport.failures` | `BroadcastFailure[]` | — | Per-chat `{ chatId, attempts, error, errorKind }` records. |
346
+
300
347
  ### Minimal bot example
301
348
 
302
349
  ```ts
@@ -434,6 +481,7 @@ interface Transport {
434
481
  | `maxBackoffMs` | `8000` | Transport delay cap. |
435
482
  | `jitter` | `0.2` | Random variation ±20% of the exponential delay. |
436
483
  | `headers` | `{}` | Additional headers. |
484
+ | `floodGate` | `true` | When Telegram answers 429, pauses NEW requests until the `retry_after` window Telegram ordered has elapsed. Never a proactive cooldown — the only waiting done is what Telegram itself demands. |
437
485
 
438
486
  ### `new FetchTransport(options?)`
439
487
 
@@ -1015,6 +1063,20 @@ new Scheduler(): Scheduler
1015
1063
 
1016
1064
  The full cron format is not supported by the built-in scheduler. Expressions other than `*/N` throw an `Error`.
1017
1065
 
1066
+ ### `Limiter` and `mapWithConcurrency`
1067
+
1068
+ ```ts
1069
+ new Limiter(limit: number): Limiter
1070
+
1071
+ mapWithConcurrency<T, R>(
1072
+ items: readonly T[],
1073
+ limit: number,
1074
+ worker: (item: T, index: number) => Promise<R>,
1075
+ ): Promise<R[]>
1076
+ ```
1077
+
1078
+ `Limiter` is a promise semaphore: tasks run immediately while a slot is free and queue FIFO beyond that. `limit` accepts any positive integer or `Infinity` (fully parallel — the library default). `mapWithConcurrency` maps items through an async worker with the same cap while preserving result order; results and errors behave like `Promise.all` mapped arrays. These primitives add no delays of their own — they only bound how many tasks run at the same time. `Limiter` exposes `activeCount` and `queuedCount` for observability.
1079
+
1018
1080
  ## 9. Plugins and services
1019
1081
 
1020
1082
  ### `Plugin<Context>`
package/docs/API.zh-CN.md CHANGED
@@ -74,6 +74,7 @@ type BotStatus =
74
74
  | `polling.allowedUpdates` | `string[]` | `[]` | Telegram 更新过滤器。 |
75
75
  | `polling.retryDelayMs` | `number` | `500` | 轮询失败时的初始延迟(毫秒)。 |
76
76
  | `polling.maxRetryDelayMs` | `number` | `30000` | 重连延迟的最大值(毫秒)。 |
77
+ | `updates.concurrency` | `number` | `Infinity` | 同时处理的 update 数量上限。不同 chat 的 update 始终并行,同一 chat 内保持顺序,因此 1000+ 条消息的突发可一次性处理。 |
77
78
 
78
79
  ### `Bot` constructor
79
80
 
@@ -201,7 +202,7 @@ launch(options?: {
201
202
  }): Promise<void>
202
203
  ```
203
204
 
204
- 以 polling 模式运行 bot。启动时生命周期依次变为 `starting` 然后 `running`,之后 `getUpdates()` 循环按顺序处理每个 update。轮询失败会触发 `polling:reconnect` 并使用指数退避。
205
+ 以 polling 模式运行 bot。启动时生命周期依次变为 `starting` 然后 `running`,之后 `getUpdates()` 循环并发处理每一批 update:不同 chat 的 update 并行执行,同一 chat 的 update 保持到达顺序。轮询失败会触发 `polling:reconnect` 并使用指数退避。
205
206
 
206
207
  除 `"polling"` 外的模式会抛出错误,并建议对 webhook 使用 `createWebhookHandler()`。
207
208
 
@@ -278,8 +279,54 @@ handleUpdate(update: Update): Promise<void>
278
279
 
279
280
  手动处理单个 update。该方法根据 `chat.id` 和 `from.id` 确定会话 key,创建 `Context`,触发 `update` 和 `message` 事件,执行 middleware 然后路由器,并在流水线完成后保存会话。
280
281
 
282
+ 不同 chat 的 update 并行处理;同一 chat 的 update 按到达顺序串行处理,因此会话、wizard 和 conversation 永远不会交错,会话写入也不会丢失。并发的 update 突发只会触发一次 `getMe` 初始化。
283
+
281
284
  流水线错误会将 bot 状态置为 `error`,触发 `bot:error`,然后重新抛出错误。
282
285
 
286
+ ### `bot.handleUpdates(updates)`
287
+
288
+ ```ts
289
+ handleUpdates(updates: readonly Update[]): Promise<void>
290
+ ```
291
+
292
+ 一次性处理整批 update:批次中的每个 chat 立即处理——跨 chat 并行、同一 chat 内按序——因此 1000 条消息的突发绝不会卡在某个慢速 handler 后面。单个 handler 的失败会记录日志、以 `update:error` 触发事件并交给 `catch()` 错误边界;它们永远不会让该 promise 被 reject。轮询循环对每个 `getUpdates` 批次都使用此方法。
293
+
294
+ ### `bot.broadcast(chatIds, send, options?)`
295
+
296
+ ```ts
297
+ broadcast(
298
+ chatIds: readonly ChatId[],
299
+ send: (chatId: ChatId) => Promise<unknown>,
300
+ options?: BroadcastOptions,
301
+ ): Promise<BroadcastReport>
302
+ ```
303
+
304
+ 并行向大量 chat 发送消息——专为向 1000+ 用户广播而设计。没有主动冷却:所有 chat(至多 `options.concurrency`,默认 `Infinity`)同时尝试发送。当 Telegram 返回 429 时,会严格按照 Telegram 指定的 `retry_after` 延迟自动重试(至多 `options.maxAttempts` 次,默认 `10`),因此突发流量会完整送达而不是失败。不可重试的错误(例如 bot 无法发送的 chat)会按 chat 记录在返回的报告中。
305
+
306
+ ```ts
307
+ const report = await bot.broadcast(
308
+ subscriberIds,
309
+ (chatId) => bot.api.methods.sendMessage({ chat_id: chatId, text: "Newsletter #42" }),
310
+ { onProgress: (progress) => console.log(`${progress.delivered}/${progress.total} delivered`) },
311
+ );
312
+ console.log(`Delivered ${report.delivered} of ${report.total} in ${report.durationMs}ms`);
313
+ for (const failure of report.failures) console.warn(`Failed: ${failure.chatId} — ${failure.error}`);
314
+ ```
315
+
316
+ #### `BroadcastOptions` 和 `BroadcastReport`
317
+
318
+ | 属性 | 类型 | 默认值 | 说明 |
319
+ |---|---|---:|---|
320
+ | `BroadcastOptions.concurrency` | `number` | `Infinity` | 同时向多少个 chat 发送消息。 |
321
+ | `BroadcastOptions.maxAttempts` | `number` | `10` | Telegram 返回 429 时每个 chat 的尝试次数。 |
322
+ | `BroadcastOptions.onProgress` | `(progress: BroadcastProgress) => void` | — | 每个 chat 结束后调用。 |
323
+ | `BroadcastOptions.signal` | `AbortSignal` | — | 中止待发送的消息;已送达的消息保持送达。 |
324
+ | `BroadcastReport.total` | `number` | — | 本次广播的 chat 总数。 |
325
+ | `BroadcastReport.delivered` | `number` | — | 成功收到消息的 chat 数。 |
326
+ | `BroadcastReport.failed` | `number` | — | 未收到消息的 chat 数。 |
327
+ | `BroadcastReport.durationMs` | `number` | — | 本次广播的实际耗时(毫秒)。 |
328
+ | `BroadcastReport.failures` | `BroadcastFailure[]` | — | 按 chat 记录的 `{ chatId, attempts, error, errorKind }`。 |
329
+
283
330
  ### 最小 bot 示例
284
331
 
285
332
  ```ts
@@ -416,6 +463,7 @@ interface Transport {
416
463
  | `backoffMs` | `250` | 初始指数退避延迟(毫秒)。 |
417
464
  | `maxBackoffMs` | `8000` | 传输延迟上限(毫秒)。 |
418
465
  | `jitter` | `0.2` | 对指数延迟的随机抖动,范围为 ±20%。 |
466
+ | `floodGate` | `true` | 当 Telegram 返回 429 时,暂停新的请求直到 Telegram 指定的 `retry_after` 窗口结束。这不是主动冷却——唯一的等待就是 Telegram 自己要求的等待。 |
419
467
  | `headers` | `{}` | 额外的请求头。 |
420
468
 
421
469
  ### `new FetchTransport(options?)`
@@ -989,6 +1037,20 @@ new Scheduler(): Scheduler
989
1037
 
990
1038
  内置调度器不支持完整的 cron 格式。除 `*/N` 外的表达式会抛出 `Error`。
991
1039
 
1040
+ ### `Limiter` 和 `mapWithConcurrency`
1041
+
1042
+ ```ts
1043
+ new Limiter(limit: number): Limiter
1044
+
1045
+ mapWithConcurrency<T, R>(
1046
+ items: readonly T[],
1047
+ limit: number,
1048
+ worker: (item: T, index: number) => Promise<R>,
1049
+ ): Promise<R[]>
1050
+ ```
1051
+
1052
+ `Limiter` 是一个 promise 信号量:有空闲槽位时任务立即执行,超出后按 FIFO 排队。`limit` 接受任意正整数或 `Infinity`(完全并行——即本库的默认值)。`mapWithConcurrency` 以相同的并发上限让 item 通过 async worker 映射,同时保持结果顺序。这些原语自身不会添加任何延迟——它们只限制同时运行的任务数量。`Limiter` 暴露 `activeCount` 和 `queuedCount` 用于可观测性。
1053
+
992
1054
  ---
993
1055
 
994
1056
  ## 9. 插件与服务
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbibzlibrary/telebibz",
3
- "version": "0.2.1",
3
+ "version": "0.3.2",
4
4
  "description": "Telegram Bot framework for Node.js and TypeScript with a typed API client, routing, middleware, webhooks, keyboards, conversations, plugins, queues, and a polished terminal experience.",
5
5
  "keywords": [
6
6
  "telegram",