supabase-js icon indicating copy to clipboard operation
supabase-js copied to clipboard

fix(realtime): wait for postgres_changes system ready before emitting SUBSCRIBED

Open 7ttp opened this issue 1 month ago • 1 comments

Summary

Fixes a race condition where SUBSCRIBED status is emitted before PostgreSQL logical replication is ready, causing early database writes to miss postgres_changes events.

Problem

The Realtime subscription has a two-stage backend process:

  1. Stage 1 (Fast): Phoenix WebSocket channel join completes
  2. Stage 2 (Slow): PostgreSQL replication slot creation and stream initialization

Previously, the client emitted SUBSCRIBED after Stage 1, but the replication stream wasn't ready until Stage 2 completed. Any database writes in this window (~1-3 seconds) would not trigger postgres_changes events.

Solution

Wait for the server's system message { extension: 'postgres_changes', status: 'ok' } before emitting SUBSCRIBED when postgres_changes bindings are present. This message is sent by the server after Realtime.Tenants.ReplicationConnection confirms the replication stream is active.

For channels without postgres_changes (broadcast/presence only), behavior is unchanged.

Changes

  • Store subscribe callback and track pending system confirmations
  • Add _maybeEmitSubscribed() that only fires when all confirmations received
  • Handle system message in _trigger() to clear pending state
  • Updated existing lifecycle tests to include system message trigger.

Related

  • Closes: https://github.com/supabase/supabase-js/issues/1599

7ttp avatar Jan 17 '26 21:01 7ttp

Coverage Status

coverage: 81.014% (+0.02%) from 80.997% when pulling 17add66d41865d08788fbb1ff1b8b2f29d1c72ff on 7ttp:fix/realtime-subscription-race-condition into 09aa10628b00cbdf65ace0f9e8e79237e7c0c1dc on supabase:master.

coveralls avatar Jan 17 '26 21:01 coveralls

@7ttp I know you and @edgurgel discussed this internally, so I am putting a "do not merge" label

mandarini avatar Jan 22 '26 09:01 mandarini