- Introduced ACTION_ALL and SUPPORTED_ACTIONS constants for better action handling.
- Updated channel subscription logic to support action suffixes.
- Added tests for action channel parsing and filtering in MessagingTest.
A phpspy profile of a production databases worker showed the V20
backwards-compat request filter accounting for ~40% of in-request
samples on `databases.listDocuments` traffic. Two compounding causes:
1. `Request::getParams()` re-ran the entire filter chain on every
invocation. The framework and app call `getParams()` several times
per request (route param binding, `cacheIdentifier()`, action
injection, logging), so V20's recursive schema walk executed N times
with identical inputs.
2. Inside `V20::getRelatedCollectionKeys`, the `databases/$databaseId`
document was fetched at every recursion frame (up to
RELATION_MAX_DEPTH = 3), and sibling relationships pointing at the
same related collection each did their own `getDocument` call.
This commit:
- Memoizes the post-filter params on `Request`. The cache is
invalidated by `addFilter`, `resetFilters`, and `setRoute`. `Request`
is constructed per HTTP request (app/http.php), so the memo is
naturally request-scoped. Helps every request filter version, not
just V20.
- Splits V20's walk into an entry point that resolves the database
namespace once and a pure recursive helper.
- Caches the collection `attributes` array per
`(databaseNamespace, collectionId)` on the filter instance, so shared
related collections collapse to one `getDocument` call. Missing or
errored lookups are cached as `null` to avoid retry storms.
Redis stringifies scalars on save, so on a cache hit the `total` field
was served as a string. Flutter SDK (and any strictly-typed client) then
failed with `TypeError: "37": type 'String' is not a subtype of type 'int'`.
The cache-miss path returned an int from `count()`, which is why only
repeat requests with `ttl > 0` tripped the bug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>