Host: one shared Neon Realtime client per address for every live read

Merged
CH
4d ago
+181-8

Before this, every liveRows read in the host opened its own Realtime client and socket. Neon's guide says to create one client for the whole app, because every subscription shares its connection (subscribing).

Supersedes #3287 with the same approach, rebuilt on main's live-follow.ts. #3287 doesn't rebase cleanly onto today's tree.

  • shareLiveClients keeps one client per Realtime URL, handed out as handles. It's loaded once however many reads wait on it. If loading fails it's forgotten, so the next read loads it again. The client closes when its last handle lets go. openRealtimeClient is this shared opener, so every area's liveRows uses it without other changes.
  • When the connection fails, Neon moves every subscription on the client to error and the client can't reconnect. So when any subscription on the shared client goes to error, the client is forgotten. The next subscribe or resubscribe opens a new one, and the old one closes once nothing holds it.
  • A sign-out or account switch (compose-account.ts's account change) calls dropRealtimeClients(). The next read opens a fresh client.
  • The polling fallback, renewals and sign-out handling in liveRows are unchanged.

A decision: the client is replaced on any error, not only a connection-level one. The client's connection error codes include whatever the server sends, so they can't be listed reliably. A per-subscription error such as a decode failure therefore costs one extra client, and the old client keeps serving the reads that hold it.

Tests (live-follow.test.ts): two reads share one client and it closes after the last one; a connection error makes the next read open a new client and the old one closes; sign-out (drop) drops the client; a failed load is retried. @luke/host tests and typecheck pass.

Made with Luke