feat: new utils dotifyQuery & nestifyQuery - #45
Open
fratzinger wants to merge 2 commits into
Open
Conversation
Convert Feathers queries between dot notation (`{ 'user.name': 'x' }`) and
nested objects (`{ user: { name: 'x' } }`). Queries arrive in both shapes
depending on where they come from, but adapters only reliably understand the
dot form, so every call site had to normalize this by hand.
Both are query-aware rather than a generic flatten/unflatten:
- operators (`$ne`, `$in`, ...) never become path segments
- `$or`/`$and`/`$nor`/`$not` branches are converted per branch
- `$sort` keys stay in dot notation in both directions — the only form adapters
understand
- `$select`, `$limit`, `$skip` and custom operators pass through untouched
Because "value vs. path" is not always decidable, both take a per-key predicate
(`descend`/`split`) plus declarative `include`/`exclude` shortcuts.
Colliding paths never lose data: deep-equal values collapse, objects with
disjoint keys merge, and a genuine contradiction is wrapped in `$and` — matching
addToQuery. A key whose path is blocked by a non-object value simply stays in
dot notation, which is already a valid condition.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`test.include` was never set, so vitest fell back to its project-wide default
(`**/*.{test,spec}.*`), which only skips `node_modules` and `dist`. The globs
that are set cover something else: `includeSource` is for in-source tests and
`coverage.include` only bounds the coverage scope.
As a result, stray checkouts — e.g. a git worktree under `.claude/` — had their
tests and type tests picked up and run. ESLint had the same problem from the
other side: `eslint .` walked into them, and since they are outside
`tsconfig.eslint.json` every file came back as a parser error.
Scope the discovery instead of blacklisting one directory, so future worktrees
and temporary clones are covered too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploying feathers-utils with
|
| Latest commit: |
24390c6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://443852f5.feathers-utils.pages.dev |
| Branch Preview URL: | https://feat-dotify-nestify-query.feathers-utils.pages.dev |
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.
Queries arrive in both dot notation (
{ 'user.name': 'x' }) and nested form ({ user: { name: 'x' } }) depending on where they come from — client, form builder, stored filter, hand-written server code. Adapters only reliably understand the dot form, so every call site had to normalize it by hand. These two utils do the conversion in both directions.Query-aware, not a generic flatten
A naive
flatten/unflattenis wrong here:$ne,$in, …) never become path segments$or/$and/$nor/$notare converted per branch, not as$or.0.…$sortkeys stay in dot notation in both directions — deliberately asymmetric, since that is the only form adapters understand$selectholds paths as values, so it passes through untouched, as do$limit,$skipand custom operatorsdotifyQueryis the reliable direction.nestifyQueryis documented as best effort: on MongoDB{ user: { name: 'x' } }means document equality while{ 'user.name': 'x' }means a subfield match.Pluggable, because "value vs. path" is not always decidable
By default a value is a path only if it is a non-empty plain object with at least one non-
$key (Date,RegExp,ObjectId, class instances, arrays,{}are values). A field may legitimately hold an object matched by equality, so both take a per-key predicate plus declarative shortcuts:The predicate wins over
include/exclude, which match the full dotted path; returningundefinedfalls through to the default heuristic. The predicate is only called where there is an actual decision to make.Collisions never lose data
Following
addToQuery, which already documents wrapping conflicting conditions in$and:dequal){$gt: 18}+{$lt: 30}$andbranch, deduped viadedupeBranchesnestifyQuery: path blocked by a non-object$andneededTwo details worth a look in review:
$andin the input cannot be clobbered by key order$andproduced inside a property value is hoisted to the enclosing level and re-keyed — a logical operator cannot sit inside a propertySecond commit: test/lint discovery
Independent of the feature, and reviewable on its own.
test.includewas never set, so vitest fell back to its project-wide default and picked up tests from stray checkouts (a git worktree under.claude/); ESLint walked into the same directories and reported every file as a parser error, since they are outsidetsconfig.eslint.json. The fix scopes discovery tosrc/testrather than blacklisting one directory. Happy to split this out if you would rather keep the PR to the feature.Verification
npm testpasses (exit 0): 1213 tests, 0 lint errors, clean typecheck, coverage 96.64% statements / 92.52% branches — 100% statements/branches/functions on all four new source files. Both.mddocs pages are discovered with workingsee:cross-links, and the tsdown build is clean.🤖 Generated with Claude Code