feat(rules)!: create channel rules as match expressions - #454
SimonWoolf wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR introduces support for the new Changes
Review Notes
|
There was a problem hiding this comment.
Review summary
The changes are correct and focused. No bugs, no architectural problems, no JSON output contract violations.
What the PR does: Channel rules now have a mode field (matcher or namespace). New rules are always created as matcher mode (match expression syntax), update intentionally omits mode from the PATCH body to preserve whatever mode a rule was created with, and all three commands (create, list, update) now surface mode in both human and JSON output.
Key correctness checks passed:
createNamespacepasses the fullnamespaceDataobject directly toJSON.stringify—mode: "matcher"will be in the POST body ✓updateNamespace's parameter type has nomodefield, so it's structurally impossible to accidentally send mode on PATCH ✓formatChannelRuleDetailsguards onrule.modebeing truthy, so pre-existing rules without a mode field don't emit a blank line ✓this.fail()used correctly throughout (neverthis.error()directly) ✓- JSON output nests data under the
rule/rulesdomain key ✓
One minor observation (not a blocker):
test/fixtures/control-api.ts line 137 defines mode?: "matcher" | "namespace" using inline string literals rather than importing ChannelRuleMode from control-api.ts. This is fine for now, but if a third mode is ever added to the type the fixture will silently fall out of sync. Worth a quick import swap:
import type { ChannelRuleMode } from "../../src/services/control-api.js";
// ...
mode?: ChannelRuleMode;But that's cosmetic — don't hold the PR for it.
The unit tests cover the three critical invariants: create always sends mode: "matcher", update never sends mode, and list exposes mode in both output formats. Ship it.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Update the parent help text and add test coverage for the update command’s JSON mode output.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Updates channel rules to support immutable matcher and legacy namespace modes, with new rules using match expressions.
Changes:
- Creates rules with
mode: "matcher". - Preserves and displays rule modes in human-readable and JSON output.
- Adds service types, formatting, fixtures, and unit tests.
| File | Summary |
|---|---|
test/unit/commands/apps/rules/update.test.ts |
Tests preserving namespace mode. |
test/unit/commands/apps/rules/list.test.ts |
Tests mode display and JSON output. |
test/unit/commands/apps/rules/create.test.ts |
Tests matcher-mode creation. |
test/fixtures/control-api.ts |
Extends fixtures with rule modes. |
src/utils/channel-rule-display.ts |
Displays rule mode. |
src/services/control-api.ts |
Adds channel rule mode types and creation support. |
src/commands/apps/rules/update.ts |
Displays the persisted rule mode. |
src/commands/apps/rules/list.ts |
Includes mode in list output. |
src/commands/apps/rules/create.ts |
Creates matcher-mode rules and updates examples. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Channel rules now come in two modes. A `matcher` rule's id is a match
expression, with the same syntax as a capability resource ("chat:*",
"*:presence", "foo:*:baz"); a `namespace` rule's id is a single segment
that matches that channel plus everything beneath it. New rules should
be matchers - the dashboard only creates matchers - so `rules create`
now always does. `rules update` sends no mode, so a rule keeps the one
it was created with. `create`, `update` and `list` show each rule's
mode, since the same-looking id means different things in each.
BREAKING CHANGE: `ably apps rules create "chat"` used to create a rule
applying to `chat` and every channel beneath it (`chat:room1`, ...). It
now applies to the channel `chat` alone. To select the channels beneath
it, use `ably apps rules create "chat:*"`. Existing rules are unaffected
and keep matching as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
7bb12ab to
dbdfb28
Compare

Important
Don't release this until ably/website#9882 is deployed. That PR adds
modeto the Control API. Before it's deployed, the Control API rejects themodethis now sends, soably apps rules createwould fail outright.Summary
Channel rules now come in two modes:
matcher: the id is a match expression, with the same syntax as a capability resource:chat:*,*:presence,foo:*:baz.namespace: the older kind. The id is one segment matching that channel plus everything beneath it.A rule's mode is fixed when it's created.
Changes
rules createalways creates amatcherrule. The help text and examples now use match expressions.rules updatesends nomode, so a rule keeps the one it was created with. This is how the dashboard behaves; the Control API rejects an attempt to change mode.create,updateandlistshow each rule'sMode, and includemodein--jsonoutput. The same-looking id means different things in each mode, solistwould otherwise be ambiguous.Breaking change
ably apps rules create "chat"used to apply tochatand every channel beneath it. It now applies tochatalone;"chat:*"selects the channels beneath it. Existing rules are unaffected. This needs a line in the release notes (the text is in the commit'sBREAKING CHANGE:footer).Debated whether the CLI should be more like the dashboard (new rules are matcher) or more like the control api (new rules are namespace with explicit mode flag to avoid breaking backcompat). ultimately given it's focused on interactive usecases decided the dashboard was the better analogy, so made it more like the dashboard where new rules are matcher mode. Let me know if you disagree.
Testing
Unit tests cover create sending
mode: matcher, update sending nomodeto a namespace-mode rule, and list/JSON showing each mode.🤖 Generated with Claude Code