Skip to content

refactor(llc): extract channel event handling into handler and state mutations - #2911

Open
VelikovPetar wants to merge 7 commits into
test/channel-event-handling-coveragefrom
refactor/extract-channel-event-handler
Open

refactor(llc): extract channel event handling into handler and state mutations#2911
VelikovPetar wants to merge 7 commits into
test/channel-event-handling-coveragefrom
refactor/extract-channel-event-handler

Conversation

@VelikovPetar

@VelikovPetar VelikovPetar commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Submit a pull request

Linear: FLU-723

Github Issue: #

CLA

  • I have signed the Stream CLA (required).
  • The code changes follow best practices
  • Code changes are tested (add some information if not applicable)

Description of the pull request

Stacked on #2905 (test/channel-event-handling-coverage) — merge that one first, then retarget this PR to master.

Moves the channel event handling out of ChannelClientState into two new internal classes, with no public API or behavior changes:

  • ChannelEventHandler — validates and routes each WS event from a single subscription (replacing the 36 per-event subscriptions), preserving the original per-subscription execution order via a three-block dispatch. Also owns the side effects an event triggers outside the channel state: member refresh on ban/unban, persisted-message cleanup on truncation, and delivery reconciliation.
  • ChannelStateMutations — owns the state writes, one semantic method per event (onMemberRemoved, onPollVoteCasted, …). The few writes that previously went through private state (typing events, watcher removal, member refresh, user message deletion) stay private on ChannelClientState and are injected as tear-offs, so the state class gains no new members.

Why these classes exist

Previously, every event listener interleaved three unrelated responsibilities: deciding whether an event applies (payload/identity/cid guards), computing the resulting state (list surgery, poll merges, unread math), and performing side effects (persistence, delivery reconciliation, member re-fetch). A backend payload change and a state-logic change would land in the same method, and none of it could be tested without a full channel lifecycle.

The split separates those along the same lines as the feeds SDK (our newest state architecture — thin event handlers that guard and route, with all mutation logic owned by semantic methods on the state side):

  • Each class now has one reason to change: event-shape concerns live in the handler; domain state rules live in the mutations.
  • The channel state's mutation surface is explicit for the first time — a reviewable list of named methods instead of logic scattered across listener bodies.
  • Write access is enforced structurally: the handler holds no state reference and cannot mutate anything; only the mutations object holds the write capability, including the five injected private paths.
  • Each layer is unit-testable in isolation: routing/guards against mocked mutations, state-write logic against a mocked state.

How this flows into v11

This is the largest subset of the v11 channel refactor achievable without breaking changes, and each piece maps forward:

  • ChannelStateMutations is the embryonic write side of v11's read-only/mutable state split — its method list is the mutation contract the mutable state owner needs, discovered and test-pinned now. The five tear-offs mark, by name, exactly which writes must become first-class members of it.
  • ChannelEventHandler is the embryonic event-bus subscriber. The three-block string-typed dispatch exists only to preserve the legacy subscription order; with v11's sealed domain events, the payload guards migrate into the typed event mapping and the ordering constraint can be consciously re-evaluated.
  • The unified REST/WS write path becomes a local change. The mutation methods take domain payloads (Message, Poll + PollVote, …), not raw events, so routing API responses through the same semantic methods — feeds' single-write-path design, the structural fix for the WS-vs-API dual-write races — only needs new plumbing, not another logic move.
  • The side effects deliberately kept in the handler (persistence cleanup, delivery reconciliation, member refresh) are exactly what becomes independent bus subscribers in v11, so they stay clearly marked in the routing layer rather than buried inside state mutations.

Net effect: v11's breaking release is left with visibility moves (hiding mutators, exposing read-only state, file split) instead of logic untangling.

Commits are structured for review: the shown-in-channel predicate extraction, the handler extraction, the handler/mutations split, and doc/changelog updates.

Testing: the event coverage added in #2905 (written against the old implementation) passes unchanged against the new one; this PR adds dedicated unit tests for the handler (60 — routing, guards, delegation) and the mutations (53 — state-write logic). Known latent issues are deliberately preserved, not fixed (e.g. member.added not deduping, the unguarded lastReadAt! in notification.mark_unread).

Screenshots / Videos

No UI changes.

VelikovPetar and others added 7 commits August 20, 2026 14:14
Replace the three hand-rolled parentId/showInChannel checks with a
single private _isShownInChannel helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move the event listener bodies out of ChannelClientState into a
ChannelEventHandler dispatching from a single subscription, preserving
the original per-subscription execution order. The state class keeps
its mutation surface unchanged; the few paths the listeners reached
through private state (typing events, watcher removal, member refresh,
user message deletion) are injected into the handler as tear-offs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ions

Reduce ChannelEventHandler to payload guards and dispatch, moving the
state-write logic into a new ChannelStateMutations owning one semantic
mutation method per event. Cover the routing and the mutations with
dedicated unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the references to the pre-refactor listener wiring and the
rationale paragraphs; document what each class and method does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rage' into refactor/extract-channel-event-handler
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 647e496b-b203-46dd-bb85-56e4efd2eef2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@VelikovPetar
VelikovPetar marked this pull request as ready for review August 20, 2026 19:11
@VelikovPetar
VelikovPetar marked this pull request as draft August 20, 2026 19:12
@VelikovPetar
VelikovPetar marked this pull request as ready for review August 20, 2026 19:12
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.57%. Comparing base (5ac9e7d) to head (7a46101).

Additional details and impacted files
@@                           Coverage Diff                            @@
##           test/channel-event-handling-coverage    #2911      +/-   ##
========================================================================
+ Coverage                                 74.55%   74.57%   +0.02%     
========================================================================
  Files                                       435      437       +2     
  Lines                                     28160    28189      +29     
========================================================================
+ Hits                                      20994    21023      +29     
  Misses                                     7166     7166              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant