docs(realtime): add MRE demonstrating JSONB filter limitation and col…#1802
docs(realtime): add MRE demonstrating JSONB filter limitation and col…#1802yash07-bit wants to merge 5 commits intosupabase:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a minimal reproducible example (MRE) to the repo documenting Supabase Realtime’s limitation with JSONB-expression filters and demonstrating the recommended “dedicated scalar column + trigger sync” workaround, plus a top-level README link to the example.
Changes:
- Add a new example (
examples/realtime-jsonb-filter-mre/) with a migration, demo runner, subscription client, and expected output. - Document the JSONB filter limitation and the recommended workaround pattern in the example README.
- Link the example from the root README and ignore the example’s local
.envfile.
Reviewed changes
Copilot reviewed 8 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds an “Examples” section linking to the new MRE. |
| .gitignore | Ignores examples/realtime-jsonb-filter-mre/.env. |
| examples/realtime-jsonb-filter-mre/subscription-client.mjs | Creates a Supabase client and a filtered postgres_changes subscription using a scalar organization_id column. |
| examples/realtime-jsonb-filter-mre/run-demo.mjs | Demo script that checks setup, subscribes, inserts a row, and reports whether events were received. |
| examples/realtime-jsonb-filter-mre/README.md | Documents the limitation, setup steps, and the workaround pattern. |
| examples/realtime-jsonb-filter-mre/package.json | Adds runnable Node demo with Supabase JS + dotenv dependencies. |
| examples/realtime-jsonb-filter-mre/package-lock.json | Lockfile for the example’s Node dependencies. |
| examples/realtime-jsonb-filter-mre/migration.sql | Creates schema/table, adds organization_id, trigger sync, index, RLS, and publication wiring. |
| examples/realtime-jsonb-filter-mre/expected-output.txt | Provides expected demo output for a successful run. |
| examples/realtime-jsonb-filter-mre/.env.example | Template env file for SUPABASE_URL / SUPABASE_ANON_KEY. |
Files not reviewed (1)
- examples/realtime-jsonb-filter-mre/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| console.log(`[INSERT] Row created with ID: ${inserted.id}`) | ||
| console.log(`[INSERT] organization_id auto-filled: ${inserted.organization_id}`) | ||
|
|
||
| if (!inserted || inserted.organization_id !== ORG_ID) { | ||
| throw new Error( | ||
| `[ERROR] Trigger sync failed: expected ${ORG_ID}, got ${inserted?.organization_id ?? 'null'}` | ||
| ) |
There was a problem hiding this comment.
inserted is dereferenced (inserted.id / inserted.organization_id) before the !inserted guard runs. If PostgREST returns an empty data array (e.g., due to RLS denying select on the inserted row), this will throw a TypeError and hide the real problem. Move the null/empty check before the logs and consider using .select(...).single() (or equivalent) so the insert either returns one row or errors explicitly.
| -- Replace with your JWT structure if needed | ||
| drop policy if exists "org based access" on pgboss.job; | ||
|
|
||
| create policy "org based access" | ||
| on pgboss.job | ||
| for select | ||
| using ( | ||
| organization_id = auth.jwt() ->> 'organization_id' | ||
| ); |
There was a problem hiding this comment.
The for select RLS policy requires a JWT claim (auth.jwt()->>'organization_id'), but the demo client uses SUPABASE_ANON_KEY and never authenticates, so select will typically be denied and Realtime events won’t be delivered (and inserts may not return rows on .select(...)). For the MRE to work out-of-the-box, either relax/selectively scope the demo select policy (e.g., allow anon/authenticated), or update the demo to sign in / supply a JWT that includes the organization_id claim and document that requirement.
| -- Replace with your JWT structure if needed | |
| drop policy if exists "org based access" on pgboss.job; | |
| create policy "org based access" | |
| on pgboss.job | |
| for select | |
| using ( | |
| organization_id = auth.jwt() ->> 'organization_id' | |
| ); | |
| -- Demo-safe select policy so the MRE works with SUPABASE_ANON_KEY | |
| -- without requiring a JWT that includes organization_id. | |
| drop policy if exists "org based access" on pgboss.job; | |
| create policy "org based access" | |
| on pgboss.job | |
| for select | |
| to anon, authenticated | |
| using (true); |
| 4. **Expected output:** | ||
| ``` | ||
| [SETUP] Supabase Realtime JSONB Filter MRE | ||
| [SUBSCRIBED] Ready to receive events | ||
| [INSERT] Creating job with JSONB data... | ||
| [WAIT] Waiting 8s for realtime event... | ||
| [subscription] event received: { ... } | ||
| [RESULT] ✅ PASS: Realtime event received with direct column filter | ||
| ``` |
There was a problem hiding this comment.
The expected output snippet doesn’t match what run-demo.mjs prints: the script logs [DEBUG] SUPABASE_URL = ..., the filter line, and a subscription status line before [SUBSCRIBED]. Update this snippet (and/or the script) so the README’s “Expected output” reflects the actual demo logs.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
What kind of change does this PR introduce?
Bug fix + docs/example update.
What is the current behavior?
Supabase Realtime
postgres_changesfiltering does not work with JSONB expressions likedata->>organization_id=eq.value.When the demo runs before the migration is applied, it also fails with a confusing schema error instead of guiding the user.
What is the new behavior?
This PR adds a minimal reproducible example showing the limitation and the production-safe workaround:
organization_idcolumn for Realtime filteringdatacolumn for flexible payload storageorganization_idfrom JSONB via a triggerAdditional context
This change is intended as a professional GitHub PR example for the Supabase Realtime repo.
It demonstrates both: