docs: Event::Messages batching and InboundDurabilityHook::on_messages (whatsapp-rust#961) - #370
Conversation
… (whatsapp-rust#961) whatsapp-rust#961 batches the inbound commit pipeline during the offline drain: Event::Message(msg, info) is now Event::Messages(MessageBatch), InboundDurabilityHook::on_message became on_messages(batch), and MessageContext::from_event was replaced by the infallible from_inbound. Updates every doc page referencing the old single-message shapes, adds batching semantics to the inbound durability guide, and documents the new store_pending_inbound_batch/delete_pending_inbound_batch methods.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
Warning Review limit reached
Next review available in: 34 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (13)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67477fa6a1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The ProtocolStore trait's store_pending_inbound/get_pending_inbound/ delete_pending_inbound/delete_expired_pending_inbound (and their batch counterparts) key on (chat, sender, id) only — verified against wacore/src/store/traits.rs and the bundled SqliteStore, which scopes by its own self.device_id field rather than accepting it as an argument. The previous examples showed a device_id parameter that doesn't exist on the trait, which a batch-delete example added for whatsapp-rust#961 then also omitted, producing a same-page inconsistency flagged in review.
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Confidence score: 3/5
- In
api/client.mdx, the exampleforloop referencesInboundMessagewithout importing it, so the sample won’t compile and users copying it will hit immediate build errors—addInboundMessageto the import list before merging.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
cubic flagged api/client.mdx's example destructuring InboundMessage without importing it. Swept the other complete/self-contained examples (full Bot::builder()...build().await? snippets and the #[tokio::main] program) for the same gap — bare match-arm fragments that never imported Event either are left as-is, matching existing doc convention.
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Auto-approved: Documentation-only changes updating examples and descriptions to reflect a batch API. No code, database, or business logic changes.
Re-trigger cubic
|
| Filename | Overview |
|---|---|
| advanced/inbound-durability.mdx | Rewritten for batch semantics: new Batching section, updated trait signature to on_messages, idempotency and redelivery sections, backend requirements, and new full example. Minor inconsistency: full example uses whatsapp_rust::types::events::InboundMessage while the trait definition section uses wacore::types::events::InboundMessage. |
| concepts/events.mdx | Message → Messages section rewritten; InboundMessage/MessageBatch/BatchOrigin types documented; imports added to EventHandler implementation example; EventKind::Messages updated throughout. |
| guides/receiving-messages.mdx | All Event::Message patterns updated to iterate over MessageBatch; durability hook example updated to on_messages; PDO recovery text updated to Event::Messages. |
| api/bot.mdx | MessageContext section updated: from_event removed, from_inbound documented as infallible; all on_event examples use Event::Messages and batch iteration. |
| guides/custom-backends.mdx | New store_pending_inbound_batch and delete_pending_inbound_batch batch methods documented with override examples and atomicity notes. |
| guides/media-handling.mdx | Updated to Event::Messages batch pattern with correct InboundMessage import and properly indented for-loop body. |
| pt/quickstart.mdx | Correctly updated to Event::Messages with InboundMessage import and batch iteration in both the main example and the step-by-step section. |
| api/client.mdx | register_handler example updated: InboundMessage added to imports and Event::Message → Event::Messages with for-loop. |
| concepts/architecture.mdx | Step 3/4 in handle_message pseudocode updated for batch commit; note on drain→live transition tail flush added; Event::Messages reference updated. |
| api/polls.mdx | Single link and anchor updated: Event::Message → Event::Messages and #message → #messages. |
| api/signal.mdx | Bot message decryption description updated from Event::Message to Event::Messages with correct anchor. |
| guides/communities.mdx | Reaction and channel comment snippets updated to Event::Messages batch iteration. |
| guides/sending-messages.mdx | MessageContext::from_event replaced with from_inbound via event.messages() iterator; comment-receiving snippet updated to batch pattern. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant WA as WhatsApp Server
participant Client as wacore Client
participant Batcher as Batch Accumulator
participant Hook as InboundDurabilityHook
participant Store as ProtocolStore
participant Handler as EventHandler
Note over WA,Handler: Live traffic (batch of 1)
WA->>Client: Encrypted stanza
Client->>Store: store_pending_inbound
Client->>Client: Signal ratchet flush
Client->>Hook: on_messages([InboundMessage])
Hook-->>Client: Ok(())
Client->>Store: delete_pending_inbound
Client->>WA: ack
Client->>Handler: "Event::Messages(batch, origin=Live)"
Note over WA,Handler: Offline drain (accumulated batch)
loop until trigger (200 msgs / 4MiB / 3s / drain end)
WA->>Client: Encrypted stanza
Client->>Batcher: accumulate InboundMessage
end
Batcher->>Store: store_pending_inbound_batch
Batcher->>Client: Signal ratchet flush (all)
Batcher->>Hook: on_messages([InboundMessage...])
Hook-->>Batcher: Ok(())
Batcher->>Store: delete_pending_inbound_batch
Batcher->>WA: acks (all)
Batcher->>Handler: "Event::Messages(batch, origin=OfflineDrain)"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant WA as WhatsApp Server
participant Client as wacore Client
participant Batcher as Batch Accumulator
participant Hook as InboundDurabilityHook
participant Store as ProtocolStore
participant Handler as EventHandler
Note over WA,Handler: Live traffic (batch of 1)
WA->>Client: Encrypted stanza
Client->>Store: store_pending_inbound
Client->>Client: Signal ratchet flush
Client->>Hook: on_messages([InboundMessage])
Hook-->>Client: Ok(())
Client->>Store: delete_pending_inbound
Client->>WA: ack
Client->>Handler: "Event::Messages(batch, origin=Live)"
Note over WA,Handler: Offline drain (accumulated batch)
loop until trigger (200 msgs / 4MiB / 3s / drain end)
WA->>Client: Encrypted stanza
Client->>Batcher: accumulate InboundMessage
end
Batcher->>Store: store_pending_inbound_batch
Batcher->>Client: Signal ratchet flush (all)
Batcher->>Hook: on_messages([InboundMessage...])
Hook-->>Batcher: Ok(())
Batcher->>Store: delete_pending_inbound_batch
Batcher->>WA: acks (all)
Batcher->>Handler: "Event::Messages(batch, origin=OfflineDrain)"
Reviews (4): Last reviewed commit: "docs(events): import InboundMessage in t..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0d564eaf82
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- guides/media-handling.mdx: add missing InboundMessage import, reindent the for-loop body one level deeper (was flush with the for statement) - guides/receiving-messages.mdx: same reindent for the text-message and DeviceSentMessage examples - concepts/events.mdx: reindent the Message Events walkthrough example; note that the event-filtering example's two if-blocks are independent filters, not mutually exclusive match arms like the original
The "Opting In" on_messages example claimed to commit the batch "in one transaction" but the per-item await loop commits each row independently — a later item's Err leaves earlier items durably committed while the SDK still redelivers the whole batch. Documents that my_db_insert must be an idempotent upsert so that redelivery replay of an already-committed item is a no-op, matching the Idempotency Requirement section below it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc1e17c7c7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for item in batch { | ||
| my_db_insert(&item.info.id, &item.message).await?; | ||
| } |
There was a problem hiding this comment.
Make the hook example batch-idempotent
This guide still shows committing each item independently with a plain my_db_insert; during an offline-drain batch, if a later insert returns Err, the SDK suppresses every ack and redelivers the whole batch, so any earlier successful inserts will be applied again unless this function is explicitly an idempotent upsert or the loop is wrapped in one transaction. The advanced durability page now calls this out, but this duplicate example can still teach users to write non-idempotent batch hooks.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Auto-approved: Updates documentation to reflect batched inbound event and hook API—no source code changes, low risk.
Re-trigger cubic
…cy note Greptile flagged pt/quickstart.mdx as missed by the whatsapp-rust#961 sweep: it still had four Event::Message(msg, info) match arms and one MessageContext::from_event call that no longer exist. Mechanically updated all five to the batch API/from_inbound, matching the pattern used across the English docs; left the page's existing on_event-based structure alone rather than restructuring it to match the newer on_message-centric English quickstart (a separate, larger sync). Codex also found the receiving-messages.mdx hook example has the same non-atomic per-item commit loop already called out in advanced/inbound-durability.mdx; added the same idempotency note here.
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Auto-approved: This PR updates documentation to reflect API changes (Event::Message -> Event::Messages, batching semantics, new types). No code or infrastructure changes; purely docs.
Re-trigger cubic
Greptile caught a real distinction: the old Event::Message(msg, info) tuple pattern needed no import, but destructuring InboundMessage as a struct pattern does. Every other converted example either already had the import or was a bare match-arm fragment; this was the one complete, self-contained snippet that needed it.
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Auto-approved: Documentation-only update renaming Event::Message to Event::Messages and on_message to on_messages with batching. No source code, config, or infrastructure changes — extremely low risk.
Re-trigger cubic
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f32d6e604d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| These triggers are internal constants, not currently exposed as configuration. The drain→live transition is raceless: the tail batch of the drain always commits before any live-mode message is processed, so a consumer never observes drain and live messages out of order across the boundary. | ||
|
|
||
| Within a batch, the commit order is: durable buffer write (one transaction) → Signal-cache flush → your hook → buffer clear → acks → `Event::Messages` dispatch. A failure at any step leaves the **entire batch** unacked, and the server redelivers all of it — so your hook must commit a batch all-or-nothing. |
There was a problem hiding this comment.
Don't promise transactional buffering for fallback batches
When a custom backend implements only the single-row pending-inbound methods, the batch methods below default to looping those calls, so the durable buffer write/delete is not one transaction unless the backend overrides the batch methods. In that scenario a mid-batch store or cleanup error can leave only part of the batch buffered/cleared while the SDK suppresses the whole batch's acks, so this sentence overstates the guarantee and contradicts the later guidance that overriding is optional; qualify the transaction claim to SqliteStore/overridden batch methods or document the fallback semantics here.
Useful? React with 👍 / 👎.
Summary
Updates the docs for oxidezap/whatsapp-rust#961, which batches the inbound commit pipeline during the offline drain. This is a breaking change to the public event/hook API:
Event::Message(Arc<wa::Message>, Arc<MessageInfo>)Event::Messages(MessageBatch)EventKind::MessageEventKind::MessagesEvent::as_message()/Event::message_text()Event::as_messages()/Event::messages()InboundDurabilityHook::on_message(info, message)InboundDurabilityHook::on_messages(batch: &[InboundMessage])MessageContext::from_event(&Event) -> Option<Self>MessageContext::from_inbound(&InboundMessage) -> Self(infallible)New types
InboundMessage,MessageBatch,BatchOriginare introduced and re-exported from the prelude.ProtocolStoregains optionalstore_pending_inbound_batch/delete_pending_inbound_batchmethods (default to looping the existing single-row methods, so third-party backends keep working).Live traffic still dispatches a batch of one (no latency change); during the offline drain, messages accumulate into a batch (200 messages / 4 MiB / 3s timeout, or forced flush at drain end) before committing, mirroring WhatsApp Web's
MessageProcessorCache.Changes
advanced/inbound-durability.mdx— rewritten for batch hook semantics: new "Batching" section (triggers, commit order, raceless drain→live transition), updated trait signature, idempotency and redelivery sections, backend requirements (batch methods), and the full worked example fromexamples/durability_hook.rs.concepts/events.mdx—Message→Messagesevent section rewritten withInboundMessage/MessageBatch/BatchOrigindefinitions; everyEvent::Messagematch/example updated to iterate a batch.guides/receiving-messages.mdx— event enum listing, all message-type examples, PDO recovery section, durability hook example, and best-practices examples updated for the batch shape.api/bot.mdx—MessageContextsection:from_eventreplaced withfrom_inbound, plus allon_event/ChannelEventHandler/complete examples.guides/custom-backends.mdx— documents the newstore_pending_inbound_batch/delete_pending_inbound_batchmethods and their default fallback.api/client.mdx,api/polls.mdx,api/signal.mdx,concepts/architecture.mdx,guides/communities.mdx,guides/media-handling.mdx,guides/sending-messages.mdx— remainingEvent::Messagereferences and anchors found via a repo-wide sweep.Changelog entries are intentionally not touched (human-authored only).
Test plan
/concepts/events#messagesand/advanced/inbound-durability#batchinganchors resolve correctlyGenerated by Claude Code
Summary by cubic
Docs adopt batched inbound commits and event batching.
Event::Messageis replaced byEvent::Messages(MessageBatch)andInboundDurabilityHook::on_messagebyon_messages, with clear batching semantics, backend guidance, and updated examples.New Features
InboundMessage,MessageBatch,BatchOrigin, plusEvent::as_messages()/Event::messages(); examples iterate a batch. Adds missingInboundMessageimports in self-contained snippets (including the firstEventHandlerexample).ProtocolStorebatch methodsstore_pending_inbound_batch/delete_pending_inbound_batch(defaults loop single-row methods);SqliteStorecommits whole batches atomically. Corrects examples to key pending-inbound on(chat, sender, id)only — nodevice_idparameter; backends scope device/account internally.on_messageshook semantics: commit the whole batch atomically or use idempotent per-item upserts — the SDK redelivers the entire batch on error. Notes thatEvent::Messagesis at-least-once when a hook is registered. Updates English and Portuguese quickstarts and fixes loop indentation.Migration
Event::Message(msg, info)→Event::Messages(batch); loopfor InboundMessage { message, info } in batch.iter(). Also switchEventKind::Message→EventKind::Messages.on_message(info, message)→on_messages(batch: &[InboundMessage]); prefer single-transaction commits, or idempotent per-item upserts.MessageContext::from_event→MessageContext::from_inbound; useevent.as_messages()/event.messages()when scanning mixed streams.Written for commit f32d6e6. Summary will update on new commits.