Skip to content

Fix sync propagation for mutating listener deletions#285

Merged
jamesgpearce merged 1 commit intotinyplex:mainfrom
ahmetkosker:fix/sync-mutating-listener-propagation
Feb 12, 2026
Merged

Fix sync propagation for mutating listener deletions#285
jamesgpearce merged 1 commit intotinyplex:mainfrom
ahmetkosker:fix/sync-mutating-listener-propagation

Conversation

@ahmetkosker
Copy link
Contributor

Summary

This change fixes a sync gap where mutations triggered by a mutating listener were not propagated back to peers. Incoming expected diffs and listener-generated local mutations are now handled separately and correctly. As a result, stores converge to the same final state

How did you test this change?

I reproduced the issue with two local synchronizer stores; before the fix they diverged, and after the fix they converged. I added a regression test for this scenario and ran targeted synchronizer tests (Local, BroadcastChannel, Custom). I also ran mergeable-store unit tests to confirm no regressions

@ahmetkosker
Copy link
Contributor Author

Summary

listener** (for example, a listener that immediately deletes a row after receiving it).
Before this fix, the receiving client applied the incoming diff and executed the listener mutation locally, but that
listener-generated mutation was not reliably propagated back to peers. In real usage, this could leave the originating
client with stale data even though the receiving client had already transformed it.

The root cause was twofold:

  • During mergeable change application, expected incoming changes and unexpected listener-generated changes were not
    explicitly separated.
    listener-generated changes created during that load cycle.

  • Tracks the exact incoming changes being applied in mergeable-store.

  • Consumes expected incoming cell/value updates without re-recording them as local edits.

  • Treats additional mutations (created by mutating listeners during the same transaction) as local changes that should
    be stamped and synced.

  • Allows synchronizers to save explicit changes even while loading, so those listener-generated mutations are

Result: all clients converge to the same final state, including cases where business logic mutates data during sync
callbacks.

How did you test this change?

I tested this with both a new regression test and targeted existing suites.

  • Added regression test:

    • test/unit/synchronizers/synchronizers.test.ts
    • Case: mutating listener deletions during sync are propagated back
    • This reproduces the bug pattern directly (incoming row -> mutating listener deletes row -> deletion must sync
      back).
  • Ran build step used by this repo’s test workflow:

    • npm run compileForTest
  • Ran targeted synchronizer suites (all passing):

    • npx vitest test/unit/synchronizers/synchronizers.test.ts -t LocalSynchronizer
    • npx vitest test/unit/synchronizers/synchronizers.test.ts -t BroadcastChannelSynchronizer
    • npx vitest test/unit/synchronizers/synchronizers.test.ts -t "Custom Synchronizer"
    • Each targeted run passed with 54 passed, 162 skipped.
  • Ran core mergeable-store regression coverage:

    • npx vitest test/unit/core/store/mergeable-store.test.ts
    • Result: 235 passed.
  • Manual runtime repro check:

    • Two local synchronizers + mutating row listener deletion scenario.
    • Verified both stores converged to the same empty table after sync.

@ahmetkosker ahmetkosker force-pushed the fix/sync-mutating-listener-propagation branch from 5e7d338 to 1a8666f Compare February 11, 2026 05:48
@jamesgpearce
Copy link
Contributor

Incredible work. Thank you so so much.

@jamesgpearce jamesgpearce merged commit 53c143b into tinyplex:main Feb 12, 2026
@jamesgpearce
Copy link
Contributor

NB this is currently breaking test/unit/synchronizers/synchronizer-ws-server.test.ts but I can fix it

@jamesgpearce
Copy link
Contributor

aaaand I found a bunch of other problems (mine!) in this area. Hopefully all good now though!

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.

2 participants