Preserve data messages during replay suppression in the TypeScript client - #4782
Preserve data messages during replay suppression in the TypeScript client#4782alco wants to merge 4 commits into
Conversation
…-date A replayed up-to-date that is suppressed during replay mode still carries a real global_last_seen_lsn. Run the snapshot tracker's lastSeenUpdate for it before filtering it out, so suppression only affects delivery to subscribers and not snapshot retirement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCuaAMyknYeVkfWMkyGuuS
The server only sets electric-cursor on live responses and the client is never in ReplayingState for a live request, so the cursor match that triggers suppression cannot occur against a conforming server. Mark the C9 test and spec entry as pinning the state-machine invariant rather than describing a server scenario. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCuaAMyknYeVkfWMkyGuuS
Drop the early return on an empty filtered batch. It skipped the subscriber callback not only for a suppressed replay marker but also for any batch whose changes were all rejected by the snapshot tracker, which previously published an empty array. Restore the pre-existing behaviour so this change is limited to the replay path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCuaAMyknYeVkfWMkyGuuS
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4782 +/- ##
===========================================
+ Coverage 60.02% 73.71% +13.69%
===========================================
Files 397 88 -309
Lines 43772 10094 -33678
Branches 12588 3105 -9483
===========================================
- Hits 26272 7441 -18831
+ Misses 17418 2595 -14823
+ Partials 82 58 -24
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a2ab2f380
ℹ️ 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".
| // A replayed up-to-date for the previous session's cursor still | ||
| // carries real WAL progress, which is applied above; only its | ||
| // delivery to subscribers is skipped. | ||
| if (transition.suppressUpToDate) return false |
There was a problem hiding this comment.
Avoid publishing an empty suppressed replay batch
When a proxy or custom endpoint triggers replay suppression with a batch containing only the duplicate up-to-date message, this filter leaves messagesToProcess empty but #publish still invokes every subscriber with []. Previously suppressBatch returned without notifying, and the public subscription documentation promises batches containing one or more messages; consumers that inspect the first or last message can therefore fail. Return without publishing when replay suppression specifically leaves no messages, while retaining the existing empty-batch behavior for snapshot-tracker filtering.
Useful? React with 👍 / 👎.
Supersedes #4723 by @joshdchang, rebased on
mainwith follow-up commits. Josh's original commit is preserved as-is.Problem
When a fresh
ShapeStreamenters replay mode (the up-to-date tracker holds a recent cursor for the shape) and the first up-to-date it sees carries that same cursor,ReplayingStaterequested suppression of the whole batch:#onMessagesreturned before publishing anything. A batch of[insert, …, up-to-date]therefore lost its rows while the stream's offset still advanced past them.Fix
suppressUpToDateinstead ofsuppressBatch. The snapshot-tracker filtering runs as usual and only the duplicateup-to-datecontrol message is removed before notifying subscribers.global_last_seen_lsnbookkeeping for a suppressed up-to-date before dropping it, so suppression affects delivery to subscribers only, not snapshot retirement.SPEC.md.The original PR also skipped the subscriber callback when the filtered batch was empty. That applied outside replay mode as well (a batch whose changes were all rejected by the snapshot tracker previously published an empty array), so it is not included here; the pre-existing behaviour is kept.
Reachability
The suppression branch cannot fire against a conforming server:
electric-cursoris only set onlive=trueresponses, the client only sendslive=truefromLiveState, andReplayingStateis only entered around a non-live request. The first up-to-date a replaying stream sees therefore never carries a cursor. The state-machine semantics were still wrong and are fixed here; the C9 test and spec entry are annotated as pinning the invariant with a synthetic cursor header rather than describing a server scenario. Whether the replay mechanism should exist at all is left for a follow-up.Fixes #4722.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PCuaAMyknYeVkfWMkyGuuS