Skip to content

feat: Add filters on the realtime subscriptions - #4

Open
ivasilov wants to merge 8 commits into
mainfrom
feat/realtime-with-filter
Open

feat: Add filters on the realtime subscriptions#4
ivasilov wants to merge 8 commits into
mainfrom
feat/realtime-with-filter

Conversation

@ivasilov

@ivasilov ivasilov commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds Realtime postgres_changes filters derived from each active query's WHERE clause, so a subscribed table only receives rows relevant to the queries currently observing it (falling back to an unfiltered/catch-all subscription when a query's conditions can't be expressed as Realtime filters).
  • Supports eq, neq, gt/gte/lt/lte (and their negations), in/not in, isNull/not isNull, and ANDed composite conditions, with PostgREST-safe value serialization (quoting, escaping) shared between the Realtime filter builder and the query builder.
  • Fixes a race between the initial PostgREST fetch and Realtime events: INSERT/UPDATE now upsert into the collection instead of throwing when a row arrives out of the expected order.
  • Adds an unfiltered UPDATE listener (so a row already in the collection keeps receiving updates after it stops matching a filter) and an unfiltered DELETE listener (Realtime only sends filtered deletes for tables with replica identity full).
  • Channel subscriptions are now rotated/namespaced by topic instead of a fixed per-table name, and swapped in only after the replacement channel finishes subscribing, so no change is missed during a filter-set change.

Test plan

  • pnpm test — 169 tests pass
  • pnpm build — builds cleanly

@ivasilov
ivasilov force-pushed the feat/realtime-with-filter branch from 1cc92d5 to 3d598fd Compare August 28, 2026 06:53
@ivasilov
ivasilov force-pushed the feat/realtime-with-filter branch from 3d598fd to 2e17006 Compare September 6, 2026 20:33
ivasilov and others added 4 commits September 11, 2026 01:15
- Match e2e channel lookup to the numbered topic names by exporting the
  topic prefix from db.ts.
- Route realtime INSERT/UPDATE through writeUpsert and gate update/delete
  on the synced store, so an echo of an in-flight local mutation no longer
  throws. Apply the same to the mutation write-backs in functions.ts.
- Only drop the previous channel once the replacement reports SUBSCRIBED.
  On CHANNEL_ERROR keep the working channel, remember the rejected filter
  set, and fall back to a catch-all subscription.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Taking main's postgrest-filters module during the rebase dropped the
realtime branch's Date->ISO conversion, so Date values were rendered via
Date.prototype.toString() (a locale string Postgres cannot cast). Route
both the raw scalar path and quoteValue through a shared toScalarString
helper, and cover the IN-members path with a test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ivasilov
ivasilov force-pushed the feat/realtime-with-filter branch from 1bbf269 to e37caa2 Compare September 10, 2026 23:21
ivasilov and others added 4 commits September 11, 2026 01:39
Gates whether each query's WHERE clause is pushed to the Realtime
subscription as a server-side postgres_changes filter. Defaults to true
(existing behavior); when false the table subscribes with a catch-all and
filters client-side, trading more Realtime traffic for simplicity and
avoiding the filter-rejection fallback path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the duplicated ISO-Date conversion and reserved-character quoting
in realtime-filters-to-search.ts with the shared PostgREST quoteValue,
gated by an isRealtimeScalar validator so null/objects/non-finite numbers
still fall back to a catch-all subscription. Surrounding whitespace is now
quoted, matching the request path.

Add unit coverage for quoting parity, bigint/Date, and non-finite
rejection, plus e2e tests that subscribe with reserved-character and
whitespace values and assert the real Realtime server accepts the quoted
filter and delivers matching inserts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add 15 e2e tests exercising the realtimeUseFilter path end to end, verifying
the real Supabase Realtime server both accepts and evaluates the filter syntax
realtimeFiltersToSearch emits (a rejected filter silently falls back to
catch-all, so this was previously mock-only):

- operator matrix: gt/gte/lt/lte, neq, not.gt, in/not.in, not.is.null, and a
  100-member IN list — matching rows delivered, non-matching excluded
- filter-window UPDATEs: a row updated out of the window stays fresh in the
  base collection (unfiltered UPDATE listener); one updated into the window is
  delivered (filtered UPDATE listener)
- DELETE delivered while a server-side filter is active
- multiple filtered queries stay isolated; an unsupported OR degrades to a
  catch-all subscription that still syncs

Add the startFilteredRealtime(ctx, ...lives) helper (preload + channel-join
wait + cleanup). is.null's positive case stays unit-only since the seed schema
has no nullable column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-side postgres_changes filtering is now opt-in: collections subscribe to
every change and filter client-side unless realtimeUseFilter is explicitly
enabled. Update the option's doc comment, point the realtime unit test helpers
that exercise filtering at realtimeUseFilter: true, and add a test locking the
new catch-all default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants