feat: accept sorts as the canonical sort input key, reject unknown build and schema keys - #906
Conversation
A mistyped build-input key was dropped without a warning, so
defineQuery({ filter: ... }) produced an empty query: downstream that
means unfiltered data, not no data. Unknown keys now raise a typed
BuildError, with a did-you-mean pointing at the canonical spelling for
the URL wire names.
Closes #905
Same failure mode as #905 on the schema side: a mistyped parameter key (field instead of fields) silently declared no allow-list at all, which is the more permissive direction.
The enum value of SORT stays 'sort', so parameters masks, describe() readers and error payloads are untouched. Resolver maps carry both spellings so either one resolves identically.
sort remains accepted and is marked deprecated. Supplying both raises a typed BuildError rather than picking a winner.
Schema.sorts and Schema.sort are the identical SortSchema instance, so the in-place index and throwOnFailure propagation is visible through both. describe() emits sorts and keeps sort through 2.x.
readParameter and skipParameter are the single choke points, so all three parser dialects gain the alias at once. Unknown keys stay ignored on the parse side: a decoded URL legitimately carries unrelated keys.
SortsSchema, defineSortsSchema, SortsOptions, SortsOptionDefault and SortsSchemaDescription match the FieldsSchema/FiltersSchema/ RelationsSchema convention. The singular names stay exported as deprecated aliases of the same value.
…ptions EntitySchemaOptions and ModelSchemaOptions are developer-authored schema input, so they take the same alias as SchemaOptions.
The wire spelling is unchanged: the URL still carries sort=-id. Encode parameter masks accept either spelling.
Documents the sorts/sort deprecation and the new unknown-key rejection. The URL wire parameter is unchanged.
Follow-up to the sorts/sort docs sweep: wire.md, filters.md, errors.md and recipes/mongo-search.md named the sort sub-schema/input key in prose or a parse-input example and were missed by the initial pass. Wire syntax (sort=...) is unchanged.
errors.md was missing KEY_UNKNOWN and KEY_AMBIGUOUS from the BuildError/SchemaError/ParseError code tables, introduced by the sorts/sort migration. sort.md's Naming tip now states that supplying both sorts and sort throws unconditionally, on defineQuery, defineSchema and every parse()/decode() call.
…rectly
ParameterSchema<'sorts', RECORD> resolved to never since the conditional
type only matched Parameter.SORT, so ResolutionScope.for(registry,
Parameter.SORTS, schema) failed to compile even though the runtime maps
already handled both spellings. Widen the branch to match both.
resolveAliasedKey used a presence-based check (own-property, including
an explicitly undefined value), so passing both spellings with one set
to undefined (e.g. { sorts: props.sorts, sort: props.sort } from a
migration wrapper) threw KEY_AMBIGUOUS even though nothing would be
dropped. Switch to a definedness check, matching how every other build
input property is read.
… edge cases Add a type-level regression guard so ParameterSchema<'sorts'|'sort'> cannot silently rot back to never. Add a direct unit spec for resolveAliasedKey covering canonical-only, alias-only, both-real (throws), neither, canonical-undefined-with-real-alias and both-undefined. Mirror adapter-typeorm's sorts-alias schema spec for adapter-prisma's defineSchemaWithModel, which had no coverage of its own alias handling. Strengthen adapter-typeorm's "reject both spellings" assertion from a bare toThrow() to SchemaError with ErrorCode.KEY_AMBIGUOUS.
…ask check The expression encoder and the simple visitor each inlined the same parameters.map((item) => normalizeParameter(item)).includes(Parameter.SORTS) expression to test whether the sort/sorts alias is present in a parameter mask. Extract includesParameter next to intersectQueryParameters, and use it at both call sites.
The earlier docs sweep updated the guide pages but missed the Packages reference section (packages/docs/packages/*.md) and the per-package npm READMEs: sample schema/parse input, per-parameter option tables and canonical-parameter-key lists still spelled the sort input key instead of sorts. parser-simple/README.md's transport-agnostic paragraph was materially wrong, not merely stale, since it listed the parser's canonical keys with the deprecated spelling. URL wire examples (sort=), page titles/links and plain-English prose are left untouched, since the wire parameter is still sort. Also make the KEY_COMBINATION_NOT_INDEXED row in guide/errors.md name both parameters explicitly (filters, sorts) instead of a bare non-backticked "sort", consistent with its neighbouring rows.
Finding 4's definedness relaxation (typeof input[key] !== 'undefined') read through the prototype chain, so an array input's inherited Array.prototype.sort satisfied the deprecated `sort` alias check: resolveAliasedKey([], 'sorts', 'sort', ...) returned the sort method instead of undefined, and defineQuery([] as any) threw inputInvalid instead of returning an empty query as it did before the fix wave. Combine the definedness check with isPropertySet so only an own, defined property counts on either side. Also strip trailing whitespace left over from an earlier commit in parser/query.ts and the schema sorts-alias spec.
|
Note Reviews pausedUse the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change makes ChangesCore API and validation
Adapter and codec integration
Documentation
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/core/src/parser/index-policy.ts (1)
274-274: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the canonical parameter value.
Replace
Parameter.SORTwithParameter.SORTS. This parser path remains an internal consumer of the deprecated alias. The replacement keeps the sameSortsSchemaresolution and removes a deprecated dependency before version 3.0.Proposed fix
- const scope = ResolutionScope.for(registry, Parameter.SORT, schema, { throwOnFailure: context.throwOnFailure }); + const scope = ResolutionScope.for(registry, Parameter.SORTS, schema, { throwOnFailure: context.throwOnFailure });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/parser/index-policy.ts` at line 274, Update the ResolutionScope.for call in the parser path to use the canonical Parameter.SORTS value instead of the deprecated Parameter.SORT alias, preserving the existing registry, schema, and throwOnFailure arguments.packages/core/test/unit/schema/sorts-alias.spec.ts (1)
40-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover undefined alias values.
Add cases for
{ sorts: {...}, sort: undefined }and{ sorts: undefined, sort: {...} }. Both inputs must construct a schema and select the defined value. This protects the required alias-resolution behavior.Proposed test cases
+ it('should ignore an undefined alias value', () => { + const canonical = defineSchema<User>({ + sorts: { allowed: ['id'] }, + sort: undefined, + } as any); + const deprecated = defineSchema<User>({ + sorts: undefined, + sort: { allowed: ['name'] }, + } as any); + + expect(canonical.sorts.allowed).toEqual(['id']); + expect(deprecated.sorts.allowed).toEqual(['name']); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/test/unit/schema/sorts-alias.spec.ts` around lines 40 - 54, Add unit tests in the alias-resolution suite covering configurations with a defined sorts value and sort: undefined, and the inverse with sort defined and sorts: undefined. Assert each constructs successfully and uses the defined alias value, while preserving the existing rejection behavior when both aliases are actually defined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/parser-simple/README.md`:
- Line 33: Update the README’s URL-query example to use the wire key sort, or
explicitly label sorts as canonical parser input. Revise the parser-key
documentation around the “only” wording to state that canonical sorts is
preferred while deprecated sort remains accepted as an alias, consistent with
the existing sorts alias behavior.
---
Nitpick comments:
In `@packages/core/src/parser/index-policy.ts`:
- Line 274: Update the ResolutionScope.for call in the parser path to use the
canonical Parameter.SORTS value instead of the deprecated Parameter.SORT alias,
preserving the existing registry, schema, and throwOnFailure arguments.
In `@packages/core/test/unit/schema/sorts-alias.spec.ts`:
- Around line 40-54: Add unit tests in the alias-resolution suite covering
configurations with a defined sorts value and sort: undefined, and the inverse
with sort defined and sorts: undefined. Assert each constructs successfully and
uses the defined alias value, while preserving the existing rejection behavior
when both aliases are actually defined.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 842e49e8-664c-4665-a830-28acded68247
📒 Files selected for processing (90)
README.mdpackages/adapter-drizzle/README.mdpackages/adapter-drizzle/test/data/schema.tspackages/adapter-memory/README.mdpackages/adapter-prisma/README.mdpackages/adapter-prisma/src/schema/assert.tspackages/adapter-prisma/src/schema/module.tspackages/adapter-prisma/src/schema/types.tspackages/adapter-prisma/test/data/schema.tspackages/adapter-prisma/test/unit/sorts-alias.spec.tspackages/adapter-sql/README.mdpackages/adapter-typeorm/README.mdpackages/adapter-typeorm/src/schema/assert.tspackages/adapter-typeorm/src/schema/module.tspackages/adapter-typeorm/src/schema/types.tspackages/adapter-typeorm/test/unit/schema/sorts-alias.spec.tspackages/codec-url/src/decoder/module.tspackages/codec-url/src/expression/encoder/module.tspackages/codec-url/src/simple/encoder/visitors/module.tspackages/codec-url/src/utils/encode.tspackages/codec-url/test/data/schema.tspackages/codec-url/test/unit/sorts-alias.spec.tspackages/core/README.mdpackages/core/src/build/module.tspackages/core/src/build/types.tspackages/core/src/constants.tspackages/core/src/errors/build.tspackages/core/src/errors/code.tspackages/core/src/errors/parse.tspackages/core/src/errors/schema.tspackages/core/src/index.tspackages/core/src/parser/index-policy.tspackages/core/src/parser/parameter/sort/types.tspackages/core/src/parser/parameter/validate.tspackages/core/src/parser/query.tspackages/core/src/parser/relation-prune.tspackages/core/src/parser/types.tspackages/core/src/schema/module.tspackages/core/src/schema/parameter/sort/define.tspackages/core/src/schema/parameter/sort/deprecated.tspackages/core/src/schema/parameter/sort/index.tspackages/core/src/schema/parameter/sort/schema.tspackages/core/src/schema/parameter/sort/types.tspackages/core/src/schema/resolver/module.tspackages/core/src/schema/resolver/types.tspackages/core/src/schema/types.tspackages/core/src/utils/index.tspackages/core/src/utils/input.tspackages/core/src/utils/parameter.tspackages/core/test/data/schema.tspackages/core/test/unit/build/module.spec.tspackages/core/test/unit/build/sorts-alias.spec.tspackages/core/test/unit/build/unknown-keys.spec.tspackages/core/test/unit/parser/parameter/key-validation.spec.tspackages/core/test/unit/schema/describe.spec.tspackages/core/test/unit/schema/sorts-alias.spec.tspackages/core/test/unit/schema/sorts-naming.spec.tspackages/core/test/unit/schema/unknown-keys.spec.tspackages/core/test/unit/types.spec.tspackages/core/test/unit/utils.spec.tspackages/docs/guide/building-queries.mdpackages/docs/guide/concepts.mdpackages/docs/guide/errors.mdpackages/docs/guide/filters.mdpackages/docs/guide/index.mdpackages/docs/guide/migration-v1.mdpackages/docs/guide/quick-start.mdpackages/docs/guide/recipes/express-typeorm.mdpackages/docs/guide/recipes/frontend.mdpackages/docs/guide/recipes/mongo-search.mdpackages/docs/guide/schemas.mdpackages/docs/guide/sort.mdpackages/docs/guide/wire.mdpackages/docs/packages/adapter-drizzle.mdpackages/docs/packages/adapter-prisma.mdpackages/docs/packages/adapter-typeorm.mdpackages/docs/packages/codec-url.mdpackages/docs/packages/core.mdpackages/docs/packages/parser-expression.mdpackages/docs/packages/parser-mongo.mdpackages/docs/packages/parser-simple.mdpackages/parser-expression/README.mdpackages/parser-expression/test/data/schema.tspackages/parser-expression/test/unit/sorts-alias.spec.tspackages/parser-mongo/README.mdpackages/parser-mongo/test/data/schema.tspackages/parser-mongo/test/unit/sorts-alias.spec.tspackages/parser-simple/README.mdpackages/parser-simple/test/data/schema.tspackages/parser-simple/test/unit/sorts-alias.spec.ts
|
@coderabbitai pause |
✅ Action performedReviews paused. |
The sorts rename left two statements inaccurate. The URL-query-shaped bullet claimed the example was the exact structure a query string yields, but a query string carries the wire key sort, not the canonical sorts. The transport-agnostic paragraph said the parser reads the canonical keys only, which no longer holds now that sort is accepted as a deprecated alias. Reported by CodeRabbit on #906.
Schema.describe() assigned the same description object to both output.sorts and output.sort, so a consumer mutating one mutated the other through the shared reference. Emit sorts only. SchemaDescription.sort is removed. The sorts-alias.spec.ts case is converted, not deleted: it now asserts output.sorts holds the description and output.sort is undefined. Docs corrected to no longer document the dual key.
Two call sites still passed the deprecated Parameter.SORT member into ResolutionScope.for, pinning the sort resolution stack to a member scheduled for removal in 3.0. Behavior is identical today since both spellings key the same schema and error classes, but the canonical member belongs here. The internal SortScope type alias in parser-simple's sorts module follows the same change.
…se vocabulary Apply the same canonical-plural pattern already used for SortsSchema to the parse-layer sort vocabulary: the plural name is canonical, the singular name stays exported as a deprecated alias of the same value or type. - core: SortParseError -> SortsParseError, SortParseOptions -> SortsParseOptions, BaseQueryParser.parseSort()/parseSortAsync() -> parseSorts()/parseSortsAsync() (the deprecated forms are now thin delegating methods). - parser-simple: SimpleSortParser -> SimpleSortsParser, SortBuildInput -> SortsBuildInput, SortBuildRecordInput -> SortsBuildRecordInput (now exported alongside its canonical form, matching the sibling FieldsBuildRecordInput convention). - codec-url: the two parser.parseSort(...) call sites in the decoder switch to the canonical parseSorts(). Internal references inside core and parser-simple were updated to the canonical names. Downstream packages (parser-mongo, parser-expression) keep extending the deprecated SimpleSortParser alias unchanged; they resolve through it like any other consumer.
…ortsParser abstract member BaseQueryParser.sortParser was the only abstract sub-parser member spelled singular; its siblings are fieldsParser/filtersParser/paginationParser/ relationsParser. Rename it to sortsParser together with its three dialect overrides, since an abstract member has no soft-deprecation path and must move in one coordinated change. Also rename applySortIndexPolicy to applySortsIndexPolicy and buildSortDefaults to buildSortsDefaults (both keep a deprecated alias), switch parser-simple's sorts module off the deprecated SortSchema/SortScope core aliases onto SortsSchema/SortsScope, and fix stray "sort"-parameter prose in core JSDoc to "sorts".
…ames ExpressionSortParser and MongoSortParser parsed the sorts parameter but were named after one member; their siblings (ExpressionFieldsParser, MongoFieldsParser, ...) are plural. Rename both to ExpressionSortsParser and MongoSortsParser, keep the old names as deprecated aliases, and route both through parser-simple's canonical SimpleSortsParser base class instead of its deprecated singular alias.
The sorts sub-adapter and its options were the only parameter area still spelled singular: ISortAdapter, SortBaseAdapter, SortAdapter, SortContainerOptions and SortInterpreterOptions, plus IRootAdapter.sort / Adapter.sort, while every sibling (fields/filters/relations/pagination) is parameter-named. QueryVisitor already assembled `this.sorts = new SortsVisitor(adapter.sort, options)`, mixing both spellings for the same thing. Rename all of them to their plural form and keep deprecated aliases. IRootAdapter now declares both `sorts` (required) and a deprecated optional `sort`; the concrete Adapter class exposes `sorts` as the real field and `sort` as a deprecated get/set accessor pair delegating to it, so an external subclass assigning `this.sort = x` keeps working. Also fix RelationInterpreterOptions to RelationsInterpreterOptions in the same visitor layer: it configures RelationsVisitor as a whole, the same bug in the relations area.
…sorts member Mirrors the adapter-sql sorts rename: SortAdapter becomes SortsAdapter (extending adapter-sql's SortsBaseAdapter, no longer the deprecated alias), and TypeormAdapter.sort becomes TypeormAdapter.sorts. The deprecated `sort` accessor is a full getter/setter pair delegating to `sorts`, so an external subclass assigning `this.sort = x` keeps working.
'sort:relation' and 'sort:numeric-name' were the only sort:* holdouts; every other featureUnsupported tag in the fleet is parameter-named plural (filters:itself, filters:negation, filters:complexity, ...). No test or doc pins the old strings.
decodeSort/encodeSort and the internal QueryVisitor.sort / QuerySerializer.sort members were the only parameter-named spots still singular; their siblings (decodeFields/encodeFields, .fields/.filters/.pagination/.relations, ...) are plural. All four are internal (BaseURLDecoder, SimpleURLEncoder, ExpressionURLEncoder and the visitor/serializer pair are not re-exported from src/index.ts), so no alias is needed. The wire constant URLParameter.SORT is untouched.
Teach the renamed canonical names (SortsParseError, SimpleSortsParser, SortsAdapter, adapter.sorts) and fix stray singular "sort"-as-parameter prose to "sorts" throughout the guide, package READMEs and .agents notes, matching the parameter's own settled vocabulary. The sort= / filter[...]= URL wire examples, the /guide/sort route and the URLParameter.SORT key column are left untouched, since those never change. Also drops a stale "sort tuple groups" claim from the ResolutionScope parser-quirks list in .agents/architecture.md: tuple groups were removed by the schema `indexes` work, so the earlier note was factually stale.
Both name the parameter itself in a list beside filters and fields, so they take the plural form the rest of the branch established.
…tParser singular for 2.x BaseQueryParser is an exported abstract class, so its protected abstract sortParser member is an extension point for custom parser dialects. Renaming it to sortsParser has no alias path (an abstract member cannot be aliased), so an external subclass implementing sortParser would stop compiling. release-please derives the version from commit types, so a refactor commit carrying that break would ship silently in a minor. The rename moves to 3.0, alongside the removal of the deprecated aliases this branch introduced.
* chore(deps): update rapiq packages to v2.1.0
Bumps @rapiq/{core,codec-url,parser-mongo,adapter-memory,adapter-sql,
adapter-typeorm} from ^2.0.0 to ^2.1.0.
The release carries a third change beyond its two headline features:
tada5hi/rapiq#906 makes `sorts` the canonical spelling on every
developer-authored surface. Two consequences had to be handled.
`describe()` renamed its `sort` key to `sorts` and, unlike the build
input / schema option / Schema property surfaces, kept no deprecated
alias. That description is served verbatim as `meta.schema`, so the
rename is wire-visible for API consumers. The URL parameter is
unchanged (`?sort=-name`).
The entity collection manager decided whether a load supersedes the
retained interactive sorts by testing `'sort' in input`, so a load
carrying the now-canonical `sorts` was silently dropped. It accepts
both spellings, matched on a DEFINED value rather than key presence:
rapiq documents `{ sorts: props.sorts, sort: props.sort }` as a safe
spread wrapper, and that shape carries both keys as `undefined`, which
a presence test reads as "supplied" and would wipe the retained sorts.
`indexed-invariant.spec.ts` reported green throughout the bump while
checking nothing: it iterated `description.sort?.allowed || []`, which
the rename turned into `undefined`, so the loop never ran and the
"every allowed sort key leads an index" invariant silently stopped
being enforced. Re-pointed, and guarded against passing vacuously
again. The invariant itself still holds.
* refactor: use the canonical rapiq sorts spelling
rapiq 2.1.0 (tada5hi/rapiq#906) renamed the sort key to `sorts` across
every developer-authored surface, keeping `sort` as a deprecated alias
slated for removal in 3.0. Migrates all authored uses: the 25 entity
schema declarations plus the query build inputs in the kit components,
the admin console and the account console.
The wire parameter is deliberately untouched. A query string still
carries `?sort=-name`, so the parse-input spellings in the decode specs
stay as they are, alongside the sibling `filter` and `page` wire names.
The collection manager keeps reading the deprecated key too. That is
not authup authoring deprecated syntax, it is tolerating it from
callers: rapiq still declares `sort` on QueryBuildInput, so dropping
the branch would silently discard a consumer's sorts.
* refactor(server-core): adopt the upstream schema index assertion
`assertSchemaIndexesMatchEntity` existed because
`assertSchemaMatchesEntity` did not cover a schema's `indexes`
declaration. rapiq 2.1.0 folded that check upstream (tada5hi/rapiq#902
closed #898), and `validateEntitySchemas` already calls the upstream
assert on the line above, so the local copy was duplication.
Upstream applies the same leftmost-prefix rule over the same primary
key, unique and index structures, and is stricter in two ways: it
resolves columns by property PATH, so embedded columns compare
correctly, and it additionally rejects an index naming a column the
entity does not have. It reports a typed SchemaEntityIndexMismatchError
instead of a bare Error.
The two tests are re-pointed at the upstream function rather than
dropped, so the boot guarantee stays pinned locally.
* fix(server-core): mint one key per realm and use under concurrency
`resolveOrCreate` is check-then-act: it looks for an active key, counts
existing rows, then inserts. Nothing made that atomic, so every
concurrent caller for one (realm, use) observed zero rows and inserted
its own key.
The callers are hot paths. The token signer resolves a signature key
for EVERY issuance and the realm cipher an encryption key for every MFA
seed, so two simultaneous logins into a freshly created realm each
minted a key. A new spec reproduces it deterministically: three
concurrent resolves produced three keys.
Concurrent mints now share a single in-flight promise, and the guarded
section re-reads before inserting, because a caller's lookup may
predate a mint that has since completed and left the map.
The map lives on the adapter instance, so it is scoped to one
application and two applications in one process never share mint state.
That scoping is also why ProvisionerModule now prefers the registered
KeyStore over constructing its own adapter, falling back only in the
minimal module graphs that never register one: a second instance
carries a second map, so the startup backfill and a concurrent
realm-create request would each still mint.
Separate processes can continue to race. The duplicate is tolerable
rather than fatal, since both keys are published in JWKS and verify and
selection is deterministically ordered, so this deliberately stops
short of a distributed lock.
* test(server-core): pin mint recovery after a failed key mint
The in-flight guard would be worse than the race it fixes if a rejected
mint stayed in the map: every later caller would receive the same
rejected promise and the realm could not mint again until the process
restarted. Covers the kill-switch rejection arriving on concurrent
resolves, and that a later resolve still succeeds.
* test(server-core): check the described query blocks per schema
The vacuity guard flattened every schema's allow-list into one array, so
a single schema losing its `filters` or `sorts` block stayed invisible:
the other schemas kept the count positive while the invariant loops'
`|| []` silently skipped the one that had gone missing. It now asserts
the block per schema and reports which schema lacks it.
Non-emptiness is deliberately NOT asserted per schema. An absent
allow-list is legitimate — `clientScope` declares no sort vocabulary, so
its `sorts.allowed` is null by design and a per-schema non-empty
assertion would fail against it. A separate aggregate check keeps the
loops from running over nothing at all.
Also corrects the architecture note, which still described the sort
override as testing key presence after it moved to a defined-value test.
Reported by CodeRabbit on #3431.
sortwas the only query parameter whose AST property (Query.sorts) differed from its input key (sort), while the build type was already calledSortsBuildInputand the schema classes were singular (SortSchema) where every sibling is plural (FieldsSchema,FiltersSchema,RelationsSchema).This makes
sortsthe canonical spelling on every developer-authored input surface, keepssortworking as@deprecated(removal in 3.0), and folds in #905.The URL wire parameter does not change: the query string still carries
sort=-id, exactly as thefiltersinput key is carried asfilter.What changed
defineQuerybuild inputsortsortscanonical,sortdeprecateddefineSchemaoptionssortsortscanonical,sortdeprecatedSchemaproperty.sort.sortscanonical,.sortthe identical instanceparse()inputsortParseQueryOptionsskip flagsortEntitySchemaOptionssortsortscanonical,sortdeprecatedModelSchemaOptionssortsortscanonical,sortdeprecateddescribe()outputsortsortsandsortsortsort(unchanged)ParametergainsSORTS = 'sorts';Parameter.SORTkeeps its value'sort'and is deprecated, soparameters: ['sort']masks,describe()readers and error payloads all keep working. Both spellings are accepted in every parameter mask.The sort schema exports are now plural (
SortsSchema,defineSortsSchema,SortsOptions,SortsOptionDefault,SortsSchemaDescription), with the singular names kept as deprecated aliases of the same values.Supplying both spellings throws a typed
KEY_AMBIGUOUSerror rather than merging or silently picking a winner. The throw is unconditional, not gated bythrowOnFailure, and applies on all three surfaces. Anundefinedside never triggers it, so a spread migration wrapper ({ sorts: props.sorts, sort: props.sort }) is safe.Closes #905
defineQuerydropped unknown top-level keys silently, sodefineQuery({ filter: ... })(the wire spelling) produced an empty and therefore unfiltered query. Unknown top-level keys now raise a typed error with a did-you-mean pointer:The same guard covers
defineSchema, where a mistyped key (field:instead offields:) silently declared no allow-list at all, the more permissive direction. Suggestions cover the wire names and obvious singulars:filtertofilters,page/limit/offsettopagination,include/relationtorelations,fieldtofields.parse()input deliberately still ignores unrelated keys: a decoded URL legitimately carries tokens and custom parameters.Release notes
This must ship as a minor, never a patch. The two
fix(core):commits make previously-working calls throw:defineQuery(someObject)carrying unrelated keys used to drop them. Do not cherry-pick8ac9bcc7or1f8e337fonto a patch line.Three observable changes worth calling out:
scope.parameteris now'sorts'for a sortvalidate/validateManyhook, where it was'sort'. Anything branching on that value needs updating.describe()output carries a newsortskey. Any downstream deep-equality snapshot of a schema description needs updating.Apps calling a parser directly on client data now have a client-triggerable
ParseErrorvia a body carrying bothsortandsorts. The documented recipe already mapsParseErrorto a 400, so it degrades correctly.New public API
Parameter.SORTS,ErrorCode.KEY_UNKNOWN,ErrorCode.KEY_AMBIGUOUS,normalizeParameter,resolveAliasedKey, the five plural sort schema exports, and thesortskeys onQueryBuildInput,SchemaOptions,ParseQueryOptions,SchemaDescription,EntitySchemaOptionsandModelSchemaOptions.assertKnownInputKeysandsuggestInputKeystay internal.Verification
Sixteen commits, each with its own tests, reviewed per commit plus a whole-branch review.
nx run-many -t build11/11,npm run test10/10 projects (2302 tests),npm run lintclean, docs site builds.Invariants checked empirically rather than from prose:
sort=-idand neversorts=Parameter.SORT === 'sort'unchangedsortspelling accepted at 2.0.0 is still acceptedschema.sorts === schema.sort(the same instance, sinceextendSchemaOptions/setIndexesmutate it in place){ sorts: false }and{ sort: false }both skip;falseis not swallowed by the definedness checkresolveAliasedKeyreads own properties only, so an array input does not seeArray.prototype.sortFollow-up
A 3.0 issue should track removing the
sortinput key on every surface,Parameter.SORT, thedescribe()sortkey and the singular class aliases. The still-singular internal vocabulary belongs in that same pass:IRootAdapter.sort,parser.parseSort(),decodeSort()/encodeSort(),SortParseError,SortParseOptions.Summary by CodeRabbit
New Features
sortsquery and schema property across the platform.sortalias for backward compatibility.sorts/sortusage.sortwire parameter.Documentation
sorts.