feat(service,features): a workspace's issues are sealed for following over Neon Realtime

Draft
CH
10d ago
+506-5

First of two PRs making the Issues list live over Neon Realtime, stacked on #3287.

What

  • issuesLiveStatement(userId, workspaceId) in issue-store.ts: the workspace's standing issues, the same columns, bound (500, as a subquery), and membership rule as listIssues, aliased with liveSelection and deterministic (no now()). Order is the reader's.
  • GET /api/live/issues?workspaceId= in live-app.ts: seals that statement for the bearer's account; invalid-request without a workspace, unavailable where Realtime is misconfigured or the seal is refused. Route file and rewrites added (functions:rewrites). The existing /api/issues route is unchanged.
  • @luke/features/issue-live: the answer and rows schemas, issuesOfLiveRows (sorts as the held read does), and HostedIssueClient.listLive.

Tests

  • PGlite parity in issue-store.test.ts: the live rows, built by issuesOfLiveRows, deep-equal listIssues for a member (a teammate's issue, a changed one, an archived one left out), and are empty for an outsider.
  • live-app.test.ts: sealed for the bearer and the workspace named; refused without a workspace; unavailable without Realtime.
  • New goldens in hosted-wire-schemas.test.ts under LUKE_UPDATE_FIXTURES=1: issue-live-issuesLiveAnswerSchema.json and issue-live-issuesLiveRowsSchema.json (new schemas only, no diffs to existing ones).

Checks run locally

lint, oxlint, knip, discarded-effect, repository checks (task names, rewrites), typecheck of database/features/service/web, and the features, issue-store, and live-app tests.

The host follow and the window push come in the next PR.

Made with Luke

After the restack

listLive names the account (?userId=), as #3287's live reads now do, and reads a 401 as LIVE_SIGNED_OUT. That is tested in issue-client.test.ts.