fix(data-objectstack,core): an object filter no longer depends on whether the query expands a lookup - #3084
Merged
Merged
Conversation
…ther the query expands a lookup #3072 single-sourced the ARRAY branch of the adapter's two `find()` routes. The object branch was left as it was: `convertQueryParams` converted a MongoDB-style filter to AST while `translateFilterToAST` returned it verbatim — so the same `$filter` went out in two formats, decided by whether the query happened to expand a lookup. Measured across 21 operator shapes, four diverged. Most of the gap turned out to be harmless, which is worth recording because it was not obvious: `{$and: […]}` survives the plain route as a `['$and','=',[…]]` comparison that `parseFilterAST` reads back as a real `$and`, and `$exists` vs `$null` is a difference the server treats identically. Two were not harmless: - THE UNKNOWN-OPERATOR GUARD ONLY RAN ON ONE ROUTE. `convertFiltersToAST` throws on an unrecognised operator, with a comment saying it does so "to avoid silent failure" — but the expanded route never called it, so a typo'd operator threw on a plain read and shipped silently whenever a lookup was expanded. - `$regex` WAS SILENTLY REWRITTEN TO `contains`. The existing test's own example makes the case: `$regex: '^John'` means "starts with John", while `contains '^John'` looks for a literal caret — so "John Smith" does not match. A different question, not a weaker version of the same one, and neither result looks wrong on screen. The rewrite sat behind a `console.warn`, which is not an error channel in a deployed app, and the function's own unknown-operator message never listed `$regex` among the supported set. The spec has no `$regex` (`FILTER_OPERATORS`, data/filter.zod.ts), so there is nothing to translate it into: it is refused now, the same treatment the neighbouring unknown operator already got. Nothing in the repo depended on the conversion. Both refusals throw `FilterOperatorError` carrying `code: 'INVALID_FILTER'` / `httpStatus: 400`. The pre-existing unknown-operator throw was a bare `Error`, which `classifyLoadError` reads as a network fault — so a malformed filter told the user to check their connection (#3066), the one thing it was not. `filter-converter.test.ts`'s `$regex` case asserted the old behaviour and is rewritten to assert the refusal, keeping the `'^John'` example because it demonstrates the harm better than any prose. Verification: 9 new/changed tests; reverting the two source files fails 9 of them. Full suite 762 files / 8875 tests green; tsc clean; eslint 0 errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last untouched half of the adapter's two
find()routes. #3072 single-sourced the array branch; the object branch still hadconvertQueryParamsconverting a MongoDB-style filter to AST whiletranslateFilterToASTreturned it verbatim — so the same$filterwent out in two formats, decided by whether the query happened to expand a lookup.Sizing it before fixing it
I measured all 21 operator shapes through both routes, comparing the server-side predicate each ends at. Four diverged — and most of the gap was harmless, which was not obvious beforehand and is worth recording:
{$and: […]}(dashboard scope filters, viamergeFilters)['$and','=',[…]], whichparseFilterASTreads back as a real$and. I expected this to be a third bug; it isn't.$existsvs$null$null, the server treats them identically$regexThe two that mattered
The unknown-operator guard only ran on one route.
convertFiltersToASTthrows on an unrecognised operator, with a comment stating it does so "to avoid silent failure". The expanded route never called it — so a typo'd operator threw on a plain read and shipped silently whenever a lookup was expanded. The guard was bypassed by exactly the condition it should have been indifferent to.$regexwas silently rewritten tocontains. The existing test's own example makes the case better than prose could:A different question, not a weaker version of the same one, and neither result looks wrong on screen. The rewrite sat behind a
console.warn— not an error channel in a deployed app — and the function's own unknown-operator error message never listed$regexamong the supported operators, so the code disagreed with itself. The spec has no$regex(FILTER_OPERATORS,data/filter.zod.ts), so there is nothing to translate it into. It is refused now, the same treatment the neighbouring unknown operator already got. Nothing in the repo depended on the conversion.Bonus: the refusals stopped blaming the network
Both now throw
FilterOperatorErrorwithcode: 'INVALID_FILTER'/httpStatus: 400. The pre-existing unknown-operator throw was a bareError, whichclassifyLoadErrorclassifies as a network fault — so a malformed filter told the user to check their connection (#3066). That was latent before this PR.A test that asserted the old behaviour
filter-converter.test.tspinned$regex → containsincluding theconsole.warn. Rewritten to assert the refusal, keeping the'^John'example — it documents the harm better than any comment.Verification
9 new/changed tests. Reverting the two source files fails 9 of them. Both routes are driven for every case rather than the helper being called directly. Full suite 762 files / 8875 tests green;
tscclean; eslint 0 errors.Refs #3072, #3081, #3066
🤖 Generated with Claude Code