OUT-3846: broadcast realtime status via DB instead of postgres_changes#112
Conversation
…changes Replace whole-row postgres_changes subscriptions with curated, secret-free DB broadcasts over private channels: - AFTER UPDATE triggers call realtime.send() with hand-picked columns only - channel_sync broadcast suppresses no-op (cursor-only) updates - client subscribes to per-portal private topics via useRealtime - snippet enables RLS on all public tables, revokes anon, and adds the anon SELECT policy on realtime.messages (run manually in SQL editor) - docs/realtime-rls.md explains the model Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryReplaces
Confidence Score: 4/5Safe to merge for the changes it makes today; the security model works correctly for all existing tables, but the anon revocation is incomplete going forward. The trigger functions, private-channel client, and realtime.messages policy are all correct for the current table set. The one concrete gap is that REVOKE ... ON ALL TABLES in the manual snippet only covers tables that exist now — the next table added via a Drizzle migration will inherit Supabase's default anon grants unless ALTER DEFAULT PRIVILEGES REVOKE is also added. supabase/snippets/2026-06-12-rls_and_anon_policy_to_realtime.sql — the REVOKE statement needs ALTER DEFAULT PRIVILEGES to cover future tables. Important Files Changed
Reviews (2): Last reviewed commit: "docs(OUT-3846): document cross-portal br..." | Re-trigger Greptile |
Make the snippet self-contained: enable RLS before creating the policy so it can't silently become a no-op in a fresh or rolled-back environment. Idempotent and safe to re-run (postgres owns it via supabase_realtime_admin). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the Record<string, unknown> + cast-to-full-row with a dedicated Pick<ChannelSyncSelectType, ...> mirroring the trigger's exact payload, so fields the broadcast never sends (dbxAccountId/dbxCursor/dbxRootId) aren't falsely typed as present. Mirrors useRealtimeDropboxConnections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make explicit that topic ACLs aren't per-portal (anon has no JWT), so any portal ID grants read access to that portal's curated payload. Add a warning against adding sensitive fields to the broadcast payloads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g casts - useRealtime: use REALTIME_LISTEN_TYPES.BROADCAST (correct overload) instead of casting 'broadcast' to the SYSTEM type, which silently typed payload as any - useRealtimeSync: type TPayload as the snake_case wire payload and derive the camelCased shape via CamelCaseKeys, removing the as-ChannelSyncSelectType cast Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add drop trigger if exists before each create trigger so the migration is safe to re-run, matching the create-or-replace style of the functions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Replaces whole-row
postgres_changesrealtime subscriptions with curated, secret-free DB broadcasts over private channels, so the browser (anon key) gets live status updates without ever reading our tables directly.What changed
AFTER UPDATE) callrealtime.send()with hand-picked, non-secret columns only:channel_sync:<portal_id>/sync_update— suppresses no-op (cursor-only) updates so the sync hot path doesn't flood clientsdropbox_connection:<portal_id>/connection_update— fires only on status changeuseRealtime) subscribes to per-portal private channels;useRealtimeSync/useRealtimeDropboxConnectionsupdated to the curated payload shapesupabase/snippets/2026-06-12-rls_and_anon_policy_to_realtime.sql(run manually in the SQL editor): enables RLS on all public tables, revokes anon access, and adds the anonSELECTpolicy onrealtime.messagesscoped to the two topicsdocs/realtime-rls.mddocuments the model.gitignore: trackdocs/, ignore.claude/settings.local.jsonWhy it's safe
postgresroleBYPASSRLSVerified
anonalready has the defaultSELECTgrant onrealtime.messages, so the RLS policy alone is sufficient (no extra GRANT needed).Deploy notes
The Drizzle migration adds the triggers/functions. The
supabase/snippets/...sqlfile must be run manually in the Supabase SQL editor (it touches therealtimeschema and performs one-time RLS/REVOKE ops) — it is not auto-applied.🤖 Generated with Claude Code