From b5f45b8fb7f2a33126f58aff847be104a933c6f6 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 28 Jul 2026 05:24:38 -0300 Subject: [PATCH 1/3] feat(realtime): add multiple postgres_changes row filters capability Flutter SDK now supports combining more than one PostgREST-style filter (AND'd) on a single postgres_changes subscription, matching supabase-js/realtime-js behavior (see supabase-flutter#1610). --- capabilities/realtime.yaml | 4 +++ .../postgres_changes_multiple_filters.md | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 specs/realtime/subscriptions/postgres_changes_multiple_filters.md diff --git a/capabilities/realtime.yaml b/capabilities/realtime.yaml index 725a732..e804a29 100644 --- a/capabilities/realtime.yaml +++ b/capabilities/realtime.yaml @@ -94,6 +94,10 @@ features: name: Postgres Changes Row Filter description: Apply a PostgREST-style filter expression (e.g. id=eq.200) to a Postgres changes subscription to receive events only for matching rows. group: subscriptions + - id: realtime.subscriptions.postgres_changes_multiple_filters + name: Multiple Postgres Changes Row Filters + description: Apply more than one PostgREST-style filter expression to a single Postgres changes subscription, combined with AND, to receive events only for rows matching all of them. + group: subscriptions - id: realtime.subscriptions.private_channel name: Private Channel (RLS) description: Mark a channel as private to enforce Row Level Security on Postgres change events and restrict broadcast and presence access to authorized users. diff --git a/specs/realtime/subscriptions/postgres_changes_multiple_filters.md b/specs/realtime/subscriptions/postgres_changes_multiple_filters.md new file mode 100644 index 0000000..e513fad --- /dev/null +++ b/specs/realtime/subscriptions/postgres_changes_multiple_filters.md @@ -0,0 +1,32 @@ +# Multiple Postgres Changes Row Filters + +## Behavior + +A single Postgres changes subscription accepts more than one PostgREST-style filter condition, combined with `AND`. Only rows matching every condition trigger an event. + +The conditions are expressed as one comma-separated filter string passed to the same `postgres_changes` binding — not as multiple separate subscriptions and not as an array field. For example, `id=eq.200,name=eq.foo` matches rows where `id` is `200` AND `name` is `foo`. + +This is a string-format capability, not a new wire message: the client passes the combined string through opaquely, and the Realtime server splits it on commas into individual `{column, operator, value, negate}` conditions before ANDing them into the underlying subscription query. + +Supported per-condition operators: `eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `in`, `like`, `ilike`, `is`, `match`, `imatch`, `isdistinct`. Any operator can be negated with a `not.` prefix (e.g. `id=not.eq.5`). + +## Prerequisites + +Requires a Realtime server version that parses comma-delimited filter strings (older servers accept only a single condition per subscription). + +## Errors + + + +## Notes + +- Only `AND` semantics — there is no way to express `OR` across conditions within one filter string. +- The `in` operator accepts at most 100 values. +- Delete events cannot be filtered at all, regardless of filter count. +- Values containing `,`, `(`, `)`, `"`, or `\` must be double-quoted so they aren't misread as a delimiter or operator boundary. +- Unrelated to the `private` channel / RLS flag — combining filters works the same on public and private channels. + +## Related + +- [Postgres Changes Row Filter](realtime.subscriptions.postgres_changes_filter) — the single-filter form this extends +- [Private Channel (RLS)](realtime.subscriptions.private_channel) From 0b43ef36bda0679e85ab31ad4faaba15b1443ed0 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 28 Jul 2026 05:33:21 -0300 Subject: [PATCH 2/3] chore(skills): add adding-a-capability project skill Documents the actual workflow for registering a new SDK capability (verify against reference implementation, mint ID, validate, spec location) after doing it live for the realtime multiple-filters capability. --- .claude/skills/adding-a-capability/SKILL.md | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .claude/skills/adding-a-capability/SKILL.md diff --git a/.claude/skills/adding-a-capability/SKILL.md b/.claude/skills/adding-a-capability/SKILL.md new file mode 100644 index 0000000..f8881b9 --- /dev/null +++ b/.claude/skills/adding-a-capability/SKILL.md @@ -0,0 +1,48 @@ +--- +name: adding-a-capability +description: Use when asked to add, register, or define a new SDK capability/feature in this repo — e.g. "add a capability for X", "this should be a new realtime/auth/storage feature", or when a client SDK PR (supabase-js, supabase-flutter, supabase-py, etc.) implements behavior not yet in capabilities/*.yaml +--- + +# Adding a Capability + +## Overview + +This repo is the canonical feature registry for Supabase client SDKs (see root `CLAUDE.md`). A "capability" is one row: an `{area}.{group}.{method}` ID plus name/description in `capabilities/{area}.yaml`, optionally backed by a prose spec under `specs/`. Getting the ID, group, and description right — grounded in what the reference SDK actually does, not just what a PR title claims — is the whole job. + +## When to Use + +- User points at an SDK PR/commit and says "this should be a capability" +- A feature exists in one SDK's public API but has no matching ID in `capabilities/` +- User asks to add a spec for an existing capability + +Not for: editing SDK compliance files (those live in each SDK's own repo), or renaming/removing existing IDs (breaking change — needs `feat!:` and cross-repo coordination, out of scope for this skill). + +## Steps + +1. **Verify the mechanism against a reference implementation before writing anything.** A PR description or issue title often describes user-facing behavior, not the actual wire/API mechanism. Check supabase-js (or the SDK's own repo) source — a client library, a server component (e.g. `supabase/realtime`), or docs at supabase.com/docs — to confirm what actually changes: new field, new combination semantics, new endpoint. Use an explorer/general-purpose agent for this if it requires cloning or grepping an external repo. +2. **Pick the area file**: `capabilities/{area}.yaml` (auth, database, storage, realtime, functions, client). Read its `groups:` list — the feature's `group` must be one of the existing group IDs, or you need to add a new group entry first. +3. **Mint the feature ID**: `{area}.{group}.{method}`, three segments, globally unique across all capability files (grep other yaml files for collisions). `method` is a short snake_case verb/noun for the specific capability, not a restatement of the group. If the new capability is a variant of an existing one (e.g. an alternate mode of an existing operation), reuse that operation's group rather than minting a new one — only add a new group when the capability doesn't belong under any existing one. +4. **Add the entry** in the `features:` list (alphabetical-ish placement near related entries is fine, exact order doesn't matter — schema doesn't enforce it): + ```yaml + - id: realtime.subscriptions.postgres_changes_multiple_filters + name: Multiple Postgres Changes Row Filters + description: Apply more than one PostgREST-style filter expression to a single Postgres changes subscription, combined with AND, to receive events only for rows matching all of them. + group: subscriptions + ``` + `description` should state observable behavior in one sentence, not implementation detail. +5. **Validate**: `cd scripts/capability-matrix && npm run validate` — catches schema errors and duplicate IDs. Fix before continuing. +6. **Optional spec file** for non-trivial behavior (branching logic, constraints, edge cases worth spelling out for implementers): `specs/{area}/{group}/{method}.md`, following `specs/TEMPLATE.md`. This is nested-directory (`area/group/method.md`), matching the actual layout of existing specs (e.g. `specs/auth/sign_in/sign_up.md`) — ignore the dotted `area.group/` example in the template comment and in root `CLAUDE.md`, they're stale relative to what's actually on disk. Keep it prose: behavior, constraints, errors — no SDK-specific function signatures. For the `Related` section, link with the bare feature ID as the href (matching real specs like `specs/realtime/subscriptions/postgres_changes_multiple_filters.md`), not the `..//.md` path the template comment suggests — that form isn't what's actually used on disk. +7. **Re-run `npm run validate`** if you added a spec (structural checks cover spec/ID linkage too). +8. **Commit** with `feat({area}): ...` (conventional commit, per root `CLAUDE.md`). + +## Common Mistakes + +- Trusting the PR title's framing instead of checking the actual mechanism (e.g. "multiple filters" sounding like multiple bindings when it's really one comma-joined filter string ANDed server-side) — leads to a wrong or misleading spec. +- Using a `group` that isn't in the area file's `groups:` list — validate will catch it, but check first to skip the round-trip. +- Writing the spec at `specs/{area}.{group}/{method}.md` (dotted, single-level) — that's what the template/CLAUDE.md say but not what's actually on disk; use nested `specs/{area}/{group}/{method}.md`. +- Forgetting `npm run validate` before committing — duplicate IDs and schema violations only surface there, not in a visual diff review. + +## Related + +- Root `CLAUDE.md` — full repo architecture, commands, commit style +- `schema/capability-matrix.schema.json` — the actual schema `npm run validate` checks against From d4a27e83ca94f4db5b739cd0af05c47c0b12e762 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Tue, 28 Jul 2026 05:38:01 -0300 Subject: [PATCH 3/3] Revert "chore(skills): add adding-a-capability project skill" This reverts commit 0b43ef36bda0679e85ab31ad4faaba15b1443ed0. --- .claude/skills/adding-a-capability/SKILL.md | 48 --------------------- 1 file changed, 48 deletions(-) delete mode 100644 .claude/skills/adding-a-capability/SKILL.md diff --git a/.claude/skills/adding-a-capability/SKILL.md b/.claude/skills/adding-a-capability/SKILL.md deleted file mode 100644 index f8881b9..0000000 --- a/.claude/skills/adding-a-capability/SKILL.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: adding-a-capability -description: Use when asked to add, register, or define a new SDK capability/feature in this repo — e.g. "add a capability for X", "this should be a new realtime/auth/storage feature", or when a client SDK PR (supabase-js, supabase-flutter, supabase-py, etc.) implements behavior not yet in capabilities/*.yaml ---- - -# Adding a Capability - -## Overview - -This repo is the canonical feature registry for Supabase client SDKs (see root `CLAUDE.md`). A "capability" is one row: an `{area}.{group}.{method}` ID plus name/description in `capabilities/{area}.yaml`, optionally backed by a prose spec under `specs/`. Getting the ID, group, and description right — grounded in what the reference SDK actually does, not just what a PR title claims — is the whole job. - -## When to Use - -- User points at an SDK PR/commit and says "this should be a capability" -- A feature exists in one SDK's public API but has no matching ID in `capabilities/` -- User asks to add a spec for an existing capability - -Not for: editing SDK compliance files (those live in each SDK's own repo), or renaming/removing existing IDs (breaking change — needs `feat!:` and cross-repo coordination, out of scope for this skill). - -## Steps - -1. **Verify the mechanism against a reference implementation before writing anything.** A PR description or issue title often describes user-facing behavior, not the actual wire/API mechanism. Check supabase-js (or the SDK's own repo) source — a client library, a server component (e.g. `supabase/realtime`), or docs at supabase.com/docs — to confirm what actually changes: new field, new combination semantics, new endpoint. Use an explorer/general-purpose agent for this if it requires cloning or grepping an external repo. -2. **Pick the area file**: `capabilities/{area}.yaml` (auth, database, storage, realtime, functions, client). Read its `groups:` list — the feature's `group` must be one of the existing group IDs, or you need to add a new group entry first. -3. **Mint the feature ID**: `{area}.{group}.{method}`, three segments, globally unique across all capability files (grep other yaml files for collisions). `method` is a short snake_case verb/noun for the specific capability, not a restatement of the group. If the new capability is a variant of an existing one (e.g. an alternate mode of an existing operation), reuse that operation's group rather than minting a new one — only add a new group when the capability doesn't belong under any existing one. -4. **Add the entry** in the `features:` list (alphabetical-ish placement near related entries is fine, exact order doesn't matter — schema doesn't enforce it): - ```yaml - - id: realtime.subscriptions.postgres_changes_multiple_filters - name: Multiple Postgres Changes Row Filters - description: Apply more than one PostgREST-style filter expression to a single Postgres changes subscription, combined with AND, to receive events only for rows matching all of them. - group: subscriptions - ``` - `description` should state observable behavior in one sentence, not implementation detail. -5. **Validate**: `cd scripts/capability-matrix && npm run validate` — catches schema errors and duplicate IDs. Fix before continuing. -6. **Optional spec file** for non-trivial behavior (branching logic, constraints, edge cases worth spelling out for implementers): `specs/{area}/{group}/{method}.md`, following `specs/TEMPLATE.md`. This is nested-directory (`area/group/method.md`), matching the actual layout of existing specs (e.g. `specs/auth/sign_in/sign_up.md`) — ignore the dotted `area.group/` example in the template comment and in root `CLAUDE.md`, they're stale relative to what's actually on disk. Keep it prose: behavior, constraints, errors — no SDK-specific function signatures. For the `Related` section, link with the bare feature ID as the href (matching real specs like `specs/realtime/subscriptions/postgres_changes_multiple_filters.md`), not the `..//.md` path the template comment suggests — that form isn't what's actually used on disk. -7. **Re-run `npm run validate`** if you added a spec (structural checks cover spec/ID linkage too). -8. **Commit** with `feat({area}): ...` (conventional commit, per root `CLAUDE.md`). - -## Common Mistakes - -- Trusting the PR title's framing instead of checking the actual mechanism (e.g. "multiple filters" sounding like multiple bindings when it's really one comma-joined filter string ANDed server-side) — leads to a wrong or misleading spec. -- Using a `group` that isn't in the area file's `groups:` list — validate will catch it, but check first to skip the round-trip. -- Writing the spec at `specs/{area}.{group}/{method}.md` (dotted, single-level) — that's what the template/CLAUDE.md say but not what's actually on disk; use nested `specs/{area}/{group}/{method}.md`. -- Forgetting `npm run validate` before committing — duplicate IDs and schema violations only surface there, not in a visual diff review. - -## Related - -- Root `CLAUDE.md` — full repo architecture, commands, commit style -- `schema/capability-matrix.schema.json` — the actual schema `npm run validate` checks against