feat(core)!: typed build layer, condition helpers & query merge - #747
Conversation
Implements plan 012 (M3), the v1 buildQuery/smob replacement: - condition helpers, one per FilterFieldOperator (reserved-word exception: in -> inArray) plus and/or compounds, with typed field paths via NestedKeys - defineQuery + per-parameter define* fragment factories desugaring typed input straight to the AST: scalars -> eq, bare arrays -> in (null is a legal element), $-operator objects, helper trees; $and/$or keys stay reserved for a future mongo dialect - immutable mergeQueries with left priority: fields/relations/sorts keyed by name, pagination per property; Filters.merge as per-field replace (flat root-AND only, typed MergeError with ErrorCode.FILTERS_NOT_FLAT) and Filters.and/or as wrap & inject for server-enforced scoping - specs per layer plus acceptance tests: URL-encoding a defineQuery-built query without magic value strings, typeorm server-injected realm scoping surviving arbitrary client input - docs: guide/build + guide/merge pages, README build section BREAKING CHANGE: the parameter node interfaces (IFields, IFilters, IPagination, IRelations, ISorts) gained merge (and, or on IFilters) methods; custom implementations must provide them. QueryBuilder is deprecated in favor of defineQuery.
📝 WalkthroughWalkthroughThis PR adds a typed build layer for query AST construction, introduces condition helpers, adds merge/combinator behavior for parameter collections and queries, and updates docs, tests, and adapter code to use the new flow. ChangesTyped build layer and query construction
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Tools execution failed with the following error: Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error) 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.
Pull request overview
This PR implements plan 012’s new typed client-side query construction and deterministic IR-level query composition in @rapiq/core, replacing v1-style build/merge patterns while keeping parsers/codecs unchanged.
Changes:
- Add typed build layer (
defineQuery+define*fragment factories) that desugars typed input directly into the Query AST. - Add typed filter condition helpers (
eq,gte,and,or,inArray, etc.) mirroring the expression dialect. - Add immutable IR merge (
mergeQueries) and per-parametermergesemantics, plusFilters.mergevsFilters.and/orsplit to support “replace vs inject” behavior.
Reviewed changes
Copilot reviewed 57 out of 57 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.MD | Update usage example to defineQuery + URL codec |
| packages/typeorm/test/unit/acceptance.spec.ts | Add acceptance test for server-injected scoping behavior |
| packages/docs/guide/query.md | Document defineQuery and helper-based construction |
| packages/docs/guide/merge.md | New guide page documenting IR merge semantics |
| packages/docs/guide/index.md | Mention new construction and merge entry points |
| packages/docs/guide/filters.md | Clarify helpers/operator objects vs wire string prefixes |
| packages/docs/guide/build.md | New guide page for typed build layer and helpers |
| packages/docs/.vitepress/config.mjs | Add Build/Merge pages to sidebar |
| packages/core/test/unit/parameter/merge.spec.ts | New unit tests for mergeQueries and filter combinators |
| packages/core/test/unit/parameter/filters-helpers.spec.ts | New unit tests for condition helper constructors |
| packages/core/test/unit/build/module.spec.ts | New unit tests for typed build layer desugaring |
| packages/core/src/parameter/sorts/collection/types.ts | Add merge() to ISorts interface |
| packages/core/src/parameter/sorts/collection/module.ts | Implement keyed, left-priority Sorts.merge() |
| packages/core/src/parameter/relations/collection/types.ts | Add merge() to IRelations interface |
| packages/core/src/parameter/relations/collection/module.ts | Implement keyed, left-priority Relations.merge() |
| packages/core/src/parameter/pagination/types.ts | Add merge() to IPagination interface |
| packages/core/src/parameter/pagination/pagination.ts | Implement per-property Pagination.merge() |
| packages/core/src/parameter/merge.ts | New mergeQueries(...queries) IR merge function |
| packages/core/src/parameter/index.ts | Export mergeQueries from parameter entrypoint |
| packages/core/src/parameter/filters/record/index.ts | Export isFilter helper from record module |
| packages/core/src/parameter/filters/record/check.ts | New isFilter() type guard for leaf filters |
| packages/core/src/parameter/filters/index.ts | Export filter helpers from filters entrypoint |
| packages/core/src/parameter/filters/helpers/module.ts | New typed condition helper implementations |
| packages/core/src/parameter/filters/helpers/index.ts | Barrel export for condition helpers |
| packages/core/src/parameter/filters/collection/types.ts | Add merge/and/or to IFilters interface |
| packages/core/src/parameter/filters/collection/module.ts | Implement Filters.merge() and injection combinators |
| packages/core/src/parameter/fields/collection/types.ts | Add merge() to IFields interface |
| packages/core/src/parameter/fields/collection/module.ts | Implement keyed, left-priority Fields.merge() |
| packages/core/src/parameter/builder.ts | Deprecate QueryBuilder in favor of typed build layer |
| packages/core/src/index.ts | Export new build layer from core entrypoint |
| packages/core/src/errors/merge.ts | New MergeError for typed merge failures |
| packages/core/src/errors/index.ts | Export MergeError |
| packages/core/src/errors/code.ts | Add FILTERS_NOT_FLAT error code |
| packages/core/src/errors/build.ts | Add typed BuildError factories (input/key/operator) |
| packages/core/src/build/utils.ts | New isParameterNode utility for fragment passthrough |
| packages/core/src/build/types.ts | New QueryBuildInput types for typed build layer |
| packages/core/src/build/parameter/sorts/types.ts | New typed sorts build input grammar |
| packages/core/src/build/parameter/sorts/module.ts | New defineSorts implementation |
| packages/core/src/build/parameter/sorts/index.ts | Barrel export for sorts build parameter |
| packages/core/src/build/parameter/relations/types.ts | New typed relations build input grammar |
| packages/core/src/build/parameter/relations/module.ts | New defineRelations implementation |
| packages/core/src/build/parameter/relations/index.ts | Barrel export for relations build parameter |
| packages/core/src/build/parameter/pagination/types.ts | New pagination build input type |
| packages/core/src/build/parameter/pagination/module.ts | New definePagination implementation |
| packages/core/src/build/parameter/pagination/index.ts | Barrel export for pagination build parameter |
| packages/core/src/build/parameter/index.ts | Barrel export for all build parameters |
| packages/core/src/build/parameter/filters/types.ts | New typed filters build input/operator grammar |
| packages/core/src/build/parameter/filters/module.ts | New defineFilters desugaring implementation |
| packages/core/src/build/parameter/filters/index.ts | Barrel export for filters build parameter |
| packages/core/src/build/parameter/fields/types.ts | New typed fields build input grammar |
| packages/core/src/build/parameter/fields/module.ts | New defineFields implementation |
| packages/core/src/build/parameter/fields/index.ts | Barrel export for fields build parameter |
| packages/core/src/build/module.ts | New defineQuery orchestration function |
| packages/core/src/build/index.ts | Export build layer public surface |
| packages/codec-url-simple/test/unit/query.spec.ts | Add encode acceptance for defineQuery-built queries |
| .agents/structure.md | Update agent structure docs for new build/merge layers |
| .agents/architecture.md | Update architecture docs to reflect plan 012 pipeline |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
defineQuery supersedes it (plan 012). BaseQueryParser assembles a
QueryContext directly; the sql adapter spec builds Query instances.
BREAKING CHANGE: the QueryBuilder export is gone — use defineQuery
(or new Query({...})) instead.
The simple wire dialect expresses flat root-AND condition sets only (subset law, plan 007 addendum). encode() now throws a typed AdapterError (ErrorCode.FEATURE_UNSUPPORTED) for or compounds and nested groups instead of silently flattening them into changed semantics. Closes the deferred plan 012 acceptance criterion. BREAKING CHANGE: URLEncoder.encode throws on non-flat filter trees that previously flattened silently.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
packages/core/src/parameter/filters/helpers/module.ts (1)
30-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReduce duplication across the 12 uniform-signature helpers.
eq/ne/lt/lte/gt/gteand the sixstartsWith-family helpers each differ only by whichFilterFieldOperatorconstant is passed. A small factory would remove ~90 lines of repetition while preserving the same public signatures.♻️ Proposed factory-based refactor
+function createComparisonHelper(operator: FilterFieldOperator) { + return function helper<RECORD extends ObjectLiteral = ObjectLiteral>( + field: FieldKey<RECORD>, + value: unknown, + ) : Filter { + return new Filter(operator, field, value); + }; +} + +function createStringHelper(operator: FilterFieldOperator) { + return function helper<RECORD extends ObjectLiteral = ObjectLiteral>( + field: FieldKey<RECORD>, + value: string, + ) : Filter { + return new Filter(operator, field, value); + }; +} + -export function eq<RECORD extends ObjectLiteral = ObjectLiteral>( - field: FieldKey<RECORD>, - value: unknown, -) : Filter { - return new Filter(FilterFieldOperator.EQUAL, field, value); -} +export const eq = createComparisonHelper(FilterFieldOperator.EQUAL); // ...repeat for ne/lt/lte/gt/gte and the startsWith-family using createStringHelperAlso applies to: 90-130
🤖 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/parameter/filters/helpers/module.ts` around lines 30 - 70, The helper functions in module.ts are duplicated wrappers that only differ by the FilterFieldOperator passed to the Filter constructor. Refactor the repeated logic behind eq, ne, lt, lte, gt, gte and the startsWith-family helpers into a small shared factory or generic creator, while preserving each function’s public signature and return type. Keep the existing exported helper names, and update the implementations to delegate to the shared helper instead of repeating the same construction code..agents/structure.md (1)
52-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBuild layer tree omits top-level files.
The
build/subtree only lists theparameter/subdirectory, but the layer's own file list includes top-levelbuild/module.ts,build/index.ts,build/types.ts, andbuild/utils.ts(thedefineQuerycomposer, barrel, and shared build-input contract). Theparameter/entry above it does list its top-levelmerge.ts/module.tsfiles, so this is an inconsistent level of detail.As per coding guidelines, "Keep `.agents` directory files (structure.md, architecture.md, testing.md, conventions.md) updated as the project evolves, including when making architectural changes, adding new patterns."📝 Suggested addition
├── build/ # typed build layer: defineQuery + per-parameter define* factories +│ ├── module.ts # defineQuery (composes per-parameter define* factories) +│ ├── types.ts # QueryBuildInput + isParameterNode guard │ └── parameter/ # Build*Input types + defineFields/defineFilters/… (schema-free, direct-to-AST)🤖 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 @.agents/structure.md around lines 52 - 53, The build/ subtree description in structure.md is incomplete and inconsistent with the rest of the tree. Update the build layer entry to include the top-level files for build/module.ts, build/index.ts, build/types.ts, and build/utils.ts alongside the existing parameter/ subtree, so the documented layer matches the actual file layout and mirrors the level of detail used for parameter/.Source: Coding guidelines
packages/core/src/parameter/relations/collection/module.ts (1)
48-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate dedup-by-name logic across collections.
This merge-by-key implementation is identical to
Sorts.merge(packages/core/src/parameter/sorts/collection/module.ts, lines 26-40), and likely mirrorsFields.mergeper the PR's stack description. Consider extracting a shared generic helper (e.g.,mergeByKey<T extends { name: string }>(a: T[], b: T[]): T[]) to avoid triplicated logic across Fields/Relations/Sorts collections.♻️ Proposed shared helper
+// e.g. packages/core/src/parameter/utils.ts +export function mergeByKey<T extends { name: string }>(a: T[], b: T[]): T[] { + const output: T[] = []; + const seen = new Set<string>(); + for (const item of [...a, ...b]) { + if (seen.has(item.name)) continue; + seen.add(item.name); + output.push(item); + } + return output; +}merge(other: IRelations) : IRelations { - const output : IRelation[] = []; - - const seen = new Set<string>(); - for (const item of [...this.value, ...other.value]) { - if (seen.has(item.name)) { - continue; - } - - seen.add(item.name); - output.push(item); - } - - return new Relations(output); + return new Relations(mergeByKey(this.value, other.value)); }🤖 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/parameter/relations/collection/module.ts` around lines 48 - 62, The merge-by-name dedup logic in Relations is duplicated across collection types and should be centralized. Extract a shared generic helper such as mergeByKey in the collection utilities and use it from Relations.merge, Sorts.merge, and the matching Fields merge implementation so all three paths share the same behavior. Keep the current name-based uniqueness semantics intact while reducing the repeated seen-set loop in each collection class.
🤖 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/core/src/build/parameter/filters/module.ts`:
- Around line 84-94: `buildFieldConditions()` is incorrectly treating nested
`Filter`/`Filters` parameter nodes as plain objects, which causes bogus
`field.operator` and `field.value` conditions; add the same `isParameterNode()`
rejection used in the `$elemMatch` path before the `isObject(value)` recursion
in `module.ts`, so helper nodes are rejected instead of expanded.
In `@packages/core/src/build/parameter/relations/module.ts`:
- Around line 34-40: In module.ts, the string handling in the Relation parsing
path currently splits on commas but does not trim each segment, so values like
"a, b" create Relation names with leading spaces. Update the input string branch
in the relation-building logic to trim each part before constructing Relation
instances, using the existing prefix-aware path in the loop that processes
input.split(',').
In `@packages/core/src/build/parameter/sorts/module.ts`:
- Around line 35-41: The string parsing in module.ts has the same comma-split
whitespace bug as the relations parser: `input.split(',')` can leave leading
spaces on each sort token, causing `pushSort` to misread descending markers like
`-name`. Update the string-handling path in the sort parsing logic to trim each
comma-separated part before passing it to `pushSort`, so `pushSort` and its
`substring(0,1)` check see the actual token.
In `@packages/docs/guide/build.md`:
- Line 50: The inline example in the build guide uses an unquoted field name,
which makes it look like a variable instead of a string key. Update the example
near the typed constructors section to use the quoted field name form, matching
the other examples in the file and the expression dialect naming. Keep the
surrounding wording intact and ensure the example with eq uses the string field
key consistently.
---
Nitpick comments:
In @.agents/structure.md:
- Around line 52-53: The build/ subtree description in structure.md is
incomplete and inconsistent with the rest of the tree. Update the build layer
entry to include the top-level files for build/module.ts, build/index.ts,
build/types.ts, and build/utils.ts alongside the existing parameter/ subtree, so
the documented layer matches the actual file layout and mirrors the level of
detail used for parameter/.
In `@packages/core/src/parameter/filters/helpers/module.ts`:
- Around line 30-70: The helper functions in module.ts are duplicated wrappers
that only differ by the FilterFieldOperator passed to the Filter constructor.
Refactor the repeated logic behind eq, ne, lt, lte, gt, gte and the
startsWith-family helpers into a small shared factory or generic creator, while
preserving each function’s public signature and return type. Keep the existing
exported helper names, and update the implementations to delegate to the shared
helper instead of repeating the same construction code.
In `@packages/core/src/parameter/relations/collection/module.ts`:
- Around line 48-62: The merge-by-name dedup logic in Relations is duplicated
across collection types and should be centralized. Extract a shared generic
helper such as mergeByKey in the collection utilities and use it from
Relations.merge, Sorts.merge, and the matching Fields merge implementation so
all three paths share the same behavior. Keep the current name-based uniqueness
semantics intact while reducing the repeated seen-set loop in each collection
class.
🪄 Autofix (Beta)
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
Run ID: 388810d5-3815-4284-a113-53fa051b19d5
📒 Files selected for processing (57)
.agents/architecture.md.agents/structure.mdREADME.MDpackages/codec-url-simple/test/unit/query.spec.tspackages/core/src/build/index.tspackages/core/src/build/module.tspackages/core/src/build/parameter/fields/index.tspackages/core/src/build/parameter/fields/module.tspackages/core/src/build/parameter/fields/types.tspackages/core/src/build/parameter/filters/index.tspackages/core/src/build/parameter/filters/module.tspackages/core/src/build/parameter/filters/types.tspackages/core/src/build/parameter/index.tspackages/core/src/build/parameter/pagination/index.tspackages/core/src/build/parameter/pagination/module.tspackages/core/src/build/parameter/pagination/types.tspackages/core/src/build/parameter/relations/index.tspackages/core/src/build/parameter/relations/module.tspackages/core/src/build/parameter/relations/types.tspackages/core/src/build/parameter/sorts/index.tspackages/core/src/build/parameter/sorts/module.tspackages/core/src/build/parameter/sorts/types.tspackages/core/src/build/types.tspackages/core/src/build/utils.tspackages/core/src/errors/build.tspackages/core/src/errors/code.tspackages/core/src/errors/index.tspackages/core/src/errors/merge.tspackages/core/src/index.tspackages/core/src/parameter/builder.tspackages/core/src/parameter/fields/collection/module.tspackages/core/src/parameter/fields/collection/types.tspackages/core/src/parameter/filters/collection/module.tspackages/core/src/parameter/filters/collection/types.tspackages/core/src/parameter/filters/helpers/index.tspackages/core/src/parameter/filters/helpers/module.tspackages/core/src/parameter/filters/index.tspackages/core/src/parameter/filters/record/check.tspackages/core/src/parameter/filters/record/index.tspackages/core/src/parameter/index.tspackages/core/src/parameter/merge.tspackages/core/src/parameter/pagination/pagination.tspackages/core/src/parameter/pagination/types.tspackages/core/src/parameter/relations/collection/module.tspackages/core/src/parameter/relations/collection/types.tspackages/core/src/parameter/sorts/collection/module.tspackages/core/src/parameter/sorts/collection/types.tspackages/core/test/unit/build/module.spec.tspackages/core/test/unit/parameter/filters-helpers.spec.tspackages/core/test/unit/parameter/merge.spec.tspackages/docs/.vitepress/config.mjspackages/docs/guide/build.mdpackages/docs/guide/filters.mdpackages/docs/guide/index.mdpackages/docs/guide/merge.mdpackages/docs/guide/query.mdpackages/typeorm/test/unit/acceptance.spec.ts
Review follow-ups (PR #747): - skip operator-object keys that are present but undefined (conditional spreads) instead of leaking conditions with undefined values - reject condition nodes used as filter field values with a typed BuildError (KEY_VALUE_INVALID) instead of expanding them like records - throw a typed BuildError on non-object pagination input, consistent with the other define* factories - trim comma-separated string input in defineFields/defineSorts/ defineRelations so "age, -name" keeps the -prefix detection intact - docs: quote the code-side field key in the condition-helper example
Calling defineSorts('age, -name') without a record generic let
TypeScript infer RECORD from the string argument itself, turning the
key grammar into nonsense (keyof string). Each define* factory and
defineQuery now declares a generic-less overload first, so untyped
calls check against the plain-string ObjectLiteral grammar; the
explicit-generic overload is unchanged. (NoInfer was rejected: it
breaks assignability of the record/tuple forms under explicit
generics.)
- quick-start builds its query with defineQuery instead of raw AST node construction; going-further links the build & merge pages - fields/sort/relations/pagination pages point to the build-layer input forms (only filters had the cross-link) - build guide documents the non-obvious operator value shapes ($regex, $mod, $exists, $elemMatch), the undefined-skip behavior for conditional spreads, and the mod/exists/elemMatch helper signatures - concepts overview lists BuildError and MergeError alongside the parse and adapter errors
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/core/src/parameter/relations/collection/module.ts (1)
43-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared dedup-merge helper.
This exact "concat, dedupe-by-name keeping first occurrence, return new immutable collection" pattern is duplicated verbatim in
Fields.merge(packages/core/src/parameter/fields/collection/module.ts), and likely in the sorts collection module too. Consider extracting a small shared utility (e.g.mergeByKey(a, b, keyFn, Ctor)) to avoid drift between the collections as merge semantics evolve.🤖 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/parameter/relations/collection/module.ts` around lines 43 - 62, The merge logic in Relations.merge duplicates the same concat-and-dedupe-by-name pattern used in Fields.merge and likely other collection modules, so factor it into a shared helper to keep semantics aligned. Extract a small reusable utility (for example a merge-by-key helper that accepts the two collections, a key selector, and a constructor) and update Relations.merge to delegate to it, preserving the existing first-occurrence, immutable behavior.packages/core/src/errors/build.ts (1)
13-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid mutating the caller-supplied options object.
Line 16 mutates
messagein place. If a caller passes a reused/sharedBaseErrorOptionsobject, this constructor will silently overwrite itsmessageproperty as a side effect.🛠️ Proposed fix
constructor(message?: string | BaseErrorOptions) { - if (isObject(message)) { - message.message = message.message || 'A building error has occurred.'; - } - - super(message || 'A building error has occurred.'); + if (isObject(message)) { + super({ ...message, message: message.message || 'A building error has occurred.' }); + return; + } + + super(message || 'A building error has occurred.'); }🤖 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/errors/build.ts` around lines 13 - 20, The BuildError constructor is mutating the caller’s BaseErrorOptions object by assigning a default message directly to message, which can leak side effects to reused options. Update BuildError in build.ts to derive a safe local options value instead of modifying the incoming argument, and pass that sanitized copy into BaseError; keep the default text behavior for BuildError while ensuring the original message object remains unchanged.
🤖 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 `@packages/core/src/errors/build.ts`:
- Around line 13-20: The BuildError constructor is mutating the caller’s
BaseErrorOptions object by assigning a default message directly to message,
which can leak side effects to reused options. Update BuildError in build.ts to
derive a safe local options value instead of modifying the incoming argument,
and pass that sanitized copy into BaseError; keep the default text behavior for
BuildError while ensuring the original message object remains unchanged.
In `@packages/core/src/parameter/relations/collection/module.ts`:
- Around line 43-62: The merge logic in Relations.merge duplicates the same
concat-and-dedupe-by-name pattern used in Fields.merge and likely other
collection modules, so factor it into a shared helper to keep semantics aligned.
Extract a small reusable utility (for example a merge-by-key helper that accepts
the two collections, a key selector, and a constructor) and update
Relations.merge to delegate to it, preserving the existing first-occurrence,
immutable behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e541921a-091b-4a93-97e0-baf7296529e3
📒 Files selected for processing (65)
.agents/architecture.md.agents/structure.mdREADME.MDpackages/codec-url-simple/src/encoder/visitors/filters.tspackages/codec-url-simple/test/unit/query.spec.tspackages/core/src/build/index.tspackages/core/src/build/module.tspackages/core/src/build/parameter/fields/index.tspackages/core/src/build/parameter/fields/module.tspackages/core/src/build/parameter/fields/types.tspackages/core/src/build/parameter/filters/index.tspackages/core/src/build/parameter/filters/module.tspackages/core/src/build/parameter/filters/types.tspackages/core/src/build/parameter/index.tspackages/core/src/build/parameter/pagination/index.tspackages/core/src/build/parameter/pagination/module.tspackages/core/src/build/parameter/pagination/types.tspackages/core/src/build/parameter/relations/index.tspackages/core/src/build/parameter/relations/module.tspackages/core/src/build/parameter/relations/types.tspackages/core/src/build/parameter/sorts/index.tspackages/core/src/build/parameter/sorts/module.tspackages/core/src/build/parameter/sorts/types.tspackages/core/src/build/types.tspackages/core/src/build/utils.tspackages/core/src/errors/build.tspackages/core/src/errors/code.tspackages/core/src/errors/index.tspackages/core/src/errors/merge.tspackages/core/src/index.tspackages/core/src/parameter/builder.tspackages/core/src/parameter/fields/collection/module.tspackages/core/src/parameter/fields/collection/types.tspackages/core/src/parameter/filters/collection/module.tspackages/core/src/parameter/filters/collection/types.tspackages/core/src/parameter/filters/helpers/index.tspackages/core/src/parameter/filters/helpers/module.tspackages/core/src/parameter/filters/index.tspackages/core/src/parameter/filters/record/check.tspackages/core/src/parameter/filters/record/index.tspackages/core/src/parameter/index.tspackages/core/src/parameter/merge.tspackages/core/src/parameter/pagination/pagination.tspackages/core/src/parameter/pagination/types.tspackages/core/src/parameter/relations/collection/module.tspackages/core/src/parameter/relations/collection/types.tspackages/core/src/parameter/sorts/collection/module.tspackages/core/src/parameter/sorts/collection/types.tspackages/core/src/parser/query.tspackages/core/test/unit/build/module.spec.tspackages/core/test/unit/parameter/filters-helpers.spec.tspackages/core/test/unit/parameter/merge.spec.tspackages/docs/.vitepress/config.mjspackages/docs/getting-started/quick-start.mdpackages/docs/guide/build.mdpackages/docs/guide/fields.mdpackages/docs/guide/filters.mdpackages/docs/guide/index.mdpackages/docs/guide/merge.mdpackages/docs/guide/pagination.mdpackages/docs/guide/query.mdpackages/docs/guide/relations.mdpackages/docs/guide/sort.mdpackages/sql/test/unit/adapter.spec.tspackages/typeorm/test/unit/acceptance.spec.ts
💤 Files with no reviewable changes (1)
- packages/core/src/parameter/builder.ts
Overview
Implements plan 012 (M3) — the typed replacement for v1's
buildQuery+ smob-based query merging. Three stacked layers, all in@rapiq/core; plus the codec-side subset guard. Parsers untouched.Layer 1 — condition helpers
One typed helper per
FilterFieldOperatorplusand/orcompounds, mirroring the expression dialect one-to-one. Field paths are typed viaNestedKeys<RECORD>when a record generic is supplied. Reserved-word exception: theINhelper is namedinArray(ninunchanged); the wire keyword staysin.Layer 2 — typed build layer
defineQuery<RECORD>+ per-parameter fragment factories (defineFields,defineFilters,definePagination,defineRelations,defineSorts) desugar typed input straight to the AST — no string round-trip, no parsing, no schema.Filters value grammar (four equivalent notations): scalar →
eq; bare array →inwithnullas a legal element (adapters own theOR IS NULLrewrite);$-operator objects ($eq…$elemMatch, unknown keys throw a typedBuildError); condition-helper trees.$and/$orobject keys stay reserved for the planned mongo parser dialect. Fragments assign intodefineQueryinput without casts.QueryBuilderis removed —defineQueryreplaces it (BaseQueryParserand the sql specs migrated toQueryContext/new Query(...)).Layer 3 — IR merge
Immutable
mergeQueries(...queries)with left priority: fields/relations/sorts keyed by name (order = first occurrence), pagination per property. Filters get two explicit operations instead of one guessed merge:Filters.merge()— per-field replace (search input overrides same-field defaults), defined only for flat root-AND trees; otherwise a typedMergeError(ErrorCode.FILTERS_NOT_FLAT)Filters.and()/or()— wrap & inject for server-enforced scoping; the wrapped tree is non-flat, so a later replace-merge throws instead of silently displacing an injected conditionCodec subset guard
URLEncoder.encode()now throws a typedAdapterError(ErrorCode.FEATURE_UNSUPPORTED) fororcompounds and nested filter groups — the simple wire dialect expresses flat root-AND sets only (subset law, plan 007 addendum). Previously these silently flattened into changed semantics.Acceptance criteria (plan 012)
encoder.encode(defineQuery({ filters: { name: { $contains: text } } }))— no~/!magic strings (codec spec)mergeQueries(searchQ, paginationQ, propsQ, defaultsQ)— same-field search filter replaces the default (core spec)eq('realm_id', …)lands in adapter SQL regardless of client input; later replace-merge cannot displace it (typeorm acceptance spec + core spec)defineQuerywithor(...)+ simple URL codecencode()→ typedFEATURE_UNSUPPORTED, not silent flattening (codec spec)Breaking changes
IFields,IFilters,IPagination,IRelations,ISorts) gainedmerge(plusand/oronIFilters) methods — custom implementations must provide them.QueryBuilderis removed — usedefineQuery(ornew Query({...})).URLEncoder.encodethrows on non-flat filter trees that previously flattened silently.Docs
New guide pages
guide/build+guide/merge(wired into the sidebar), cross-links + "coming from v1" notes on the filters/query/overview pages (incl. the compound-transport warning), README build section rewritten to thedefineQuery+URLEncoderarchetype.Test plan
npm run build— all 7 projectsnpx nx run-many -t test --skip-nx-cache— 470 tests green across 6 packages (new: helper spec, build spec, merge spec, codec encode + subset-guard acceptance, typeorm scoping acceptance)Summary by CodeRabbit
New Features
defineQuery) with fragment factories for fields, filters, sorting, relations, and pagination.eq,gte,and,or, etc.) and query composition viamergeQuerieswith immutable left-priority semantics.Bug Fixes
Documentation
Tests