feat(service,host): hold a live read's poll until its rows move

Merged
CH
5h ago
+389-100

Stacks on #4267, which turns Realtime off by default. Today, with Realtime off, a live read polls every 3 s. This PR makes the poll path near-instant: the service holds each poll until the rows move.

Service (live-procedures.ts, realtime.ts, held-read.ts)

  • Digests: each polled part now carries a digest of its rows.
  • Holding a poll: a poll can now carry after= (liveDigestOf: each part's digest, in the parts' name order). The service holds it, re-running the statements every 1 s:
    • when the rows move, it answers them at once;
    • after 25 s, it answers "unchanged": the digests and no rows.
  • Reuse: the hold reuses held-read.ts. Its loop is now heldUntil, and heldUntilMoved is built on it.
  • Auth: a poll is authorized exactly as the seal is, through the same procedures, with no new route.

Host (live-follow.ts)

  • Re-asking: liveRows sends the digest it last saw and re-asks after each answer, waiting at least 500 ms between asks. A failed poll is asked again 1 s later, then after longer gaps, up to 5 s. Polling stops when the follow ends or the read is gone.
  • When the service answers polled: this is the case where Realtime is off or the service couldn't seal. The read is polled quietly and isn't resubscribed every 15 s. This addresses Devin's finding on #4267. A Realtime failure on the host itself still logs and resubscribes.
  • Areas: the board and the transcript only pass after through to their clients. askLive sends a poll with after under the held deadline (35 s).

Tests

  • Service: a held poll answers within a beat of a change, and answers "unchanged" with no rows at 25 s.
  • Host:
    • a read answered polled is polled quietly, each poll naming the last digest, and is never resealed;
    • a failed poll backs off 1 s, 2 s, 4 s, then 5 s.
  • Board and transcript: their existing follow tests still pass.

Made with Luke


Devin Review