feat: schema index declarations backed by entity indexes (rapiq 2.0.0-beta.20) - #3425
Conversation
The release ships schema index declarations (tada5hi/rapiq#895) and closes the pre-GA decode-hardening gaps (tada5hi/rapiq#893): a pagination key carrying a prototype segment now raises a typed ParseError instead of being ignored, and routup's qs passes 'prototype' segments through, so the throw is reachable over HTTP. sanitizeError maps rapiq ParseError/CodecError onto BAD_REQUEST: a decode failure is client wire input, not a server fault. The expression dialect has always thrown on disallowed keys, so those requests answered 500 before this. Pinned over HTTP for both paths.
…ndexes Every registered schema declares its `indexes` and opts into the rapiq 2.0.0-beta.20 indexed filters (anchor mode) and sort policies, under one invariant: every allowed filter/sort key LEADS a declared index backed by a real database structure, so enforcement never rejects a query the allow-lists permit. The only narrowing is a multi-key sort without a matching composite prefix, which drops whole-parameter (no UI surface sends one). Migration 1786436332251-QueryIndexes backs the declarations: 126 new indexes covering the query vocabulary, every remaining FK scalar column (junction *_realm_id and policy_id columns, client.access_policy_id, the EA and identity-provider mapping tables; a Postgres-only gap since MySQL keeps implicit FK indexes), the login-throttle composite (actor_name, request_ip_address, created_at) replacing the redundant actor_name single, session_tokens.parent_id (grace-window lookup), and the reset_hash/activate_hash token lookups (sole selective predicate on unauthenticated endpoints). It also drops the three orphaned legacy tables (auth_authorization_codes, auth_refresh_tokens, auth_identity_provider_roles); down() recreates them exactly as the pre-existing chain leaves them. The mysql down() wraps the generated statements with hand-authored FK drop/re-add phases: MySQL silently drops a constraint's implicit index once a created index can serve it, so the plain DROP INDEX would fail with ER 1553 on 50 constraints. assertSchemaIndexesMatchEntity joins the boot-time validation pass (a declared sequence must be a leftmost prefix of a real PK/unique/index; tada5hi/rapiq#898 tracks upstreaming it). Verified on both dialects: run, revert x16, re-run, schema-drift gate, populated round-trip.
Query IR flow gains the index-declaration rules (adding a filterable or sortable key now requires a backing entity index plus migration); conventions and the typeorm reference record that MySQL auto-drops a foreign key's implicit index when a created index can serve the constraint, breaking generated down() paths, and the wrap pattern that restores the exact pre-migration state.
|
Warning Review limit reached
Next review available in: 16 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThe query architecture now requires entity-backed schema indexes and indexed filter and sort declarations. TypeORM entities and database migrations add the required indexes. Boot validation, query decoding, error mapping, tests, documentation, and Rapiq dependencies were updated. ChangesIndexed query contracts
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant QueryClient
participant QueryDecoder
participant EntitySchema
participant Database
QueryClient->>QueryDecoder: submit filters and sorts
QueryDecoder->>EntitySchema: validate indexed fields and prefixes
EntitySchema->>Database: execute query using declared indexes
Database-->>QueryClient: return matching entities
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 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 |
authup
@authup/client-account-console
@authup/client-admin-console
@authup/client-auth-console
@authup/server-core
@authup/access
@authup/client-web-kit
@authup/client-web-kit-theme
@authup/client-web-nuxt
@authup/client-web-theme
@authup/core-http-kit
@authup/core-kit
@authup/core-realtime-kit
@authup/errors
@authup/i18n
@authup/kit
@authup/server-adapter-kit
@authup/server-adapter-node
@authup/server-adapter-socket-io
@authup/server-adapter-web
@authup/server-kit
@authup/server-test-kit
@authup/specs
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/server-core/test/unit/core/query/module.spec.ts (1)
404-412: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the retained sort field and direction.
The length-only assertion passes if decoding retains a sort other than
-createdAt. Assert that the retained entry representscreatedAtin descending order.Verify the
Querysort entry shape for@rapiq/core2.0.0-beta.20 before adding the assertion. Runnpm run buildbefore the repository test command. As per coding guidelines, “Runnpm run buildbefore testing; use the repository-provided npm scripts for building, testing, and linting.”🤖 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 `@apps/server-core/test/unit/core/query/module.spec.ts` around lines 404 - 412, Update the test “should keep an allowed single-key filter and sort” to assert the retained sort entry’s field is createdAt and its direction is descending, using the Query sort-entry shape from `@rapiq/core` 2.0.0-beta.20. Replace or supplement the length-only assertion while preserving the existing filter assertion, then run npm run build before the repository test command.Source: Coding guidelines
🤖 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.
Nitpick comments:
In `@apps/server-core/test/unit/core/query/module.spec.ts`:
- Around line 404-412: Update the test “should keep an allowed single-key filter
and sort” to assert the retained sort entry’s field is createdAt and its
direction is descending, using the Query sort-entry shape from `@rapiq/core`
2.0.0-beta.20. Replace or supplement the length-only assertion while preserving
the existing filter assertion, then run npm run build before the repository test
command.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4045ccc3-a369-437c-8d35-80b99b5103f1
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (74)
.agents/architecture.md.agents/conventions.md.agents/references/typeorm.mdapps/client-account-console/package.jsonapps/client-admin-console/package.jsonapps/server-core/package.jsonapps/server-core/src/adapters/database/domains/client-permission/entity.tsapps/server-core/src/adapters/database/domains/client-role/entity.tsapps/server-core/src/adapters/database/domains/client-scope/entity.tsapps/server-core/src/adapters/database/domains/client/entity.tsapps/server-core/src/adapters/database/domains/consent/entity.tsapps/server-core/src/adapters/database/domains/event/entity.tsapps/server-core/src/adapters/database/domains/identity-provider-account/entity.tsapps/server-core/src/adapters/database/domains/identity-provider-attribute-mapping/entity.tsapps/server-core/src/adapters/database/domains/identity-provider-attribute/entity.tsapps/server-core/src/adapters/database/domains/identity-provider-permission-mapping/entity.tsapps/server-core/src/adapters/database/domains/identity-provider-role-mapping/entity.tsapps/server-core/src/adapters/database/domains/identity-provider/entity.tsapps/server-core/src/adapters/database/domains/key/entity.tsapps/server-core/src/adapters/database/domains/permission-policy/entity.tsapps/server-core/src/adapters/database/domains/permission/entity.tsapps/server-core/src/adapters/database/domains/policy-attribute/entity.tsapps/server-core/src/adapters/database/domains/policy/entity.tsapps/server-core/src/adapters/database/domains/realm/entity.tsapps/server-core/src/adapters/database/domains/role-attribute/entity.tsapps/server-core/src/adapters/database/domains/role-permission/entity.tsapps/server-core/src/adapters/database/domains/role/entity.tsapps/server-core/src/adapters/database/domains/scope/entity.tsapps/server-core/src/adapters/database/domains/session-token/entity.tsapps/server-core/src/adapters/database/domains/session/entity.tsapps/server-core/src/adapters/database/domains/trust-anchor/entity.tsapps/server-core/src/adapters/database/domains/user-attribute/entity.tsapps/server-core/src/adapters/database/domains/user-authenticator/entity.tsapps/server-core/src/adapters/database/domains/user-permission/entity.tsapps/server-core/src/adapters/database/domains/user-role/entity.tsapps/server-core/src/adapters/database/domains/user/entity.tsapps/server-core/src/adapters/database/migrations/mysql/1786436332251-QueryIndexes.tsapps/server-core/src/adapters/database/migrations/postgres/1786436332251-QueryIndexes.tsapps/server-core/src/app/modules/database/repositories/schema-validation.tsapps/server-core/src/core/entities/client-permission/schema.tsapps/server-core/src/core/entities/client-role/schema.tsapps/server-core/src/core/entities/client-scope/schema.tsapps/server-core/src/core/entities/client/schema.tsapps/server-core/src/core/entities/consent/schema.tsapps/server-core/src/core/entities/event/schema.tsapps/server-core/src/core/entities/identity-provider-account/schema.tsapps/server-core/src/core/entities/identity-provider-role-mapping/schema.tsapps/server-core/src/core/entities/identity-provider/schema.tsapps/server-core/src/core/entities/key/schema.tsapps/server-core/src/core/entities/permission-policy/schema.tsapps/server-core/src/core/entities/permission/schema.tsapps/server-core/src/core/entities/policy/schema.tsapps/server-core/src/core/entities/realm/schema.tsapps/server-core/src/core/entities/role-attribute/schema.tsapps/server-core/src/core/entities/role-permission/schema.tsapps/server-core/src/core/entities/role/schema.tsapps/server-core/src/core/entities/scope/schema.tsapps/server-core/src/core/entities/session-token/schema.tsapps/server-core/src/core/entities/session/schema.tsapps/server-core/src/core/entities/trust-anchor/schema.tsapps/server-core/src/core/entities/user-attribute/schema.tsapps/server-core/src/core/entities/user-authenticator/schema.tsapps/server-core/src/core/entities/user-permission/schema.tsapps/server-core/src/core/entities/user-role/schema.tsapps/server-core/src/core/entities/user/schema.tsapps/server-core/src/utils/error.tsapps/server-core/test/unit/app/modules/database/schema-validation.spec.tsapps/server-core/test/unit/core/query/module.spec.tsapps/server-core/test/unit/http/controllers/entities/query-decode-errors.spec.tspackages/access/package.jsonpackages/client-web-kit/package.jsonpackages/core-http-kit/package.jsonpackages/server-kit/package.jsonpackages/server-test-kit/package.json
A clean re-tag of 2.0.0-beta.20 - no content changes; the full suite re-verified against the resolved release artifacts.
Summary
Adopts rapiq 2.0.0-beta.20's schema index declarations (tada5hi/rapiq#895) end to end and hardens the decode path.
rapiq bump + decode failures answer 400
@rapiq/*^2.0.0-beta.19->^2.0.0-beta.20across all 8 workspaces. The removed sort tuple groups were unused (no-op here).sanitizeErrormaps rapiqParseError/CodecErrorontoBAD_REQUEST: the expression dialect has always thrown on disallowed keys (previously a 500), and beta.20's pagination prototype-key guard widened the throwing surface (routup's qs passesprototypesegments through). Pinned over HTTP.Index declarations + enforcement
Every registered schema declares
indexesand opts intofilters: { indexed: true }(anchor mode) +sort: { indexed: true }, under one invariant: every allowed filter/sort key leads a declared index backed by a real database structure — enforcement can never reject a query the allow-lists permit. The only observable narrowing is a multi-key sort without a matching composite prefix, which drops whole-parameter (fail-soft; no UI surface sends one). Corollary, now documented: adding a filterable/sortable key requires a backing entity index + migration.Boot-time
assertSchemaIndexesMatchEntityfails the start when a declared sequence is not a leftmost prefix of a real PK/unique/index (assertSchemaMatchesEntitydoes not coverindexesyet — tada5hi/rapiq#898).Migration
1786436332251-QueryIndexes*_realm_id/policy_id,client.access_policy_id, EA + IdP mapping tables — a Postgres-only gap, MySQL keeps implicit FK indexes; realm deletion no longer full-scans every junction), the login-throttle composite(actor_name, request_ip_address, created_at)(replaces the redundantactor_namesingle),session_tokens.parent_id(grace-windowhasConsumedChild), the federated-login pair(provider_user_id, provider_id), andreset_hash/activate_hash(sole selective predicate on unauthenticated endpoints, were full scans).auth_authorization_codes,auth_refresh_tokens,auth_identity_provider_roles);down()recreates them exactly as the pre-existing chain leaves them so every olderdown()path keeps working.down()wraps the generated statements with hand-authored FK drop/re-add phases: MySQL silently drops a constraint's implicit index once a created index can serve it, so a plainDROP INDEXfails with ER 1553 on 50 constraints (pattern + affected-set query documented in conventions and the typeorm reference). Naming: all constraint names come from typeorm 1.1.0's built-in default strategy viamigration generate— no naming-strategy override, no legacy-naming-strategies dependency.Verification
migration run->revertx16 ->run, schema-drift gate green, populated round-trip 12/12 (test:migration-latest).check:typesclean.describe()surface (decode), index-drift assert (unit).Summary by CodeRabbit
New Features
Bug Fixes
400 Bad Requestresponses.Documentation