Skip to content

feat(core)!: typed build layer, condition helpers & query merge - #747

Merged
tada5hi merged 6 commits into
masterfrom
feat/build-layer-and-ir-merge
Jul 7, 2026
Merged

feat(core)!: typed build layer, condition helpers & query merge#747
tada5hi merged 6 commits into
masterfrom
feat/build-layer-and-ir-merge

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 7, 2026

Copy link
Copy Markdown
Owner

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 FilterFieldOperator plus and/or compounds, mirroring the expression dialect one-to-one. Field paths are typed via NestedKeys<RECORD> when a record generic is supplied. Reserved-word exception: the IN helper is named inArray (nin unchanged); the wire keyword stays in.

const conditions = and(
    eq('name', 'John'),
    or(gte('age', 18), eq('deleted_at', null)),
);

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.

const query = defineQuery<User>({
    fields: ['id', 'name'],
    filters: { name: { $contains: text }, realm_id: [id, null] },
    relations: ['realm'],
    sort: '-created_at',
    pagination: { limit: 10 },
});

Filters value grammar (four equivalent notations): scalar → eq; bare array → in with null as a legal element (adapters own the OR IS NULL rewrite); $-operator objects ($eq$elemMatch, unknown keys throw a typed BuildError); condition-helper trees. $and/$or object keys stay reserved for the planned mongo parser dialect. Fragments assign into defineQuery input without casts. QueryBuilder is removeddefineQuery replaces it (BaseQueryParser and the sql specs migrated to QueryContext/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 typed MergeError (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 condition

Codec subset guard

URLEncoder.encode() now throws a typed AdapterError (ErrorCode.FEATURE_UNSUPPORTED) for or compounds 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)

  • ✅ Entity client: encoder.encode(defineQuery({ filters: { name: { $contains: text } } })) — no ~/! magic strings (codec spec)
  • ✅ Vue list kit: mergeQueries(searchQ, paginationQ, propsQ, defaultsQ) — same-field search filter replaces the default (core spec)
  • ✅ Server scoping: injected eq('realm_id', …) lands in adapter SQL regardless of client input; later replace-merge cannot displace it (typeorm acceptance spec + core spec)
  • ✅ Fragments assign cleanly without casts (core spec)
  • ✅ Compound round-trip guard: defineQuery with or(...) + simple URL codec encode() → typed FEATURE_UNSUPPORTED, not silent flattening (codec spec)

Breaking changes

  • The parameter node interfaces (IFields, IFilters, IPagination, IRelations, ISorts) gained merge (plus and/or on IFilters) methods — custom implementations must provide them.
  • QueryBuilder is removed — use defineQuery (or new Query({...})).
  • URLEncoder.encode throws 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 the defineQuery + URLEncoder archetype.

Test plan

  • npm run build — all 7 projects
  • npx 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)
  • eslint clean on all touched files

Summary by CodeRabbit

  • New Features

    • Added a typed client query-building workflow (defineQuery) with fragment factories for fields, filters, sorting, relations, and pagination.
    • Added filter condition helpers (eq, gte, and, or, etc.) and query composition via mergeQueries with immutable left-priority semantics.
  • Bug Fixes

    • URL encoding now rejects unsupported compound/nested filter structures instead of attempting to flatten them.
    • Improved typed error codes and merge/filters validation behavior.
  • Documentation

    • Updated README and guides (build, query, filters, merge, quick start) and navigation to reflect the new workflow.
  • Tests

    • Added unit and acceptance coverage for building, encoding, merging, and merge/error edge cases.

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.
Copilot AI review requested due to automatic review settings July 7, 2026 10:01
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

Typed build layer and query construction

Layer / File(s) Summary
Build contracts and entrypoints
packages/core/src/build/types.ts, packages/core/src/build/utils.ts, packages/core/src/build/parameter/*/types.ts, packages/core/src/build/*/index.ts, packages/core/src/index.ts
QueryBuildInput, per-parameter build-input types, isParameterNode, and the build-layer barrel exports are added.
defineQuery and parameter factories
packages/core/src/build/module.ts, packages/core/src/build/parameter/*/module.ts, packages/core/src/errors/build.ts, packages/core/src/errors/code.ts, packages/core/src/parser/query.ts, packages/sql/test/unit/adapter.spec.ts, packages/core/test/unit/build/module.spec.ts
defineFields, defineFilters, definePagination, defineRelations, defineSorts, and defineQuery are implemented; build errors gain typed factories; parser/SQL tests move to Query.
Condition helpers and encoding checks
packages/core/src/parameter/filters/helpers/*, packages/core/src/parameter/filters/record/check.ts, packages/core/src/parameter/filters/index.ts, packages/codec-url-simple/src/encoder/visitors/filters.ts, packages/codec-url-simple/test/unit/query.spec.ts, packages/core/test/unit/parameter/filters-helpers.spec.ts
Typed helpers (eq, inArray, and, or, etc.) build Filter/Filters nodes directly, with an isFilter guard and codec tests for unsupported compound filters.
Merge combinators and mergeQueries
packages/core/src/parameter/*/collection/module.ts, packages/core/src/parameter/*/collection/types.ts, packages/core/src/parameter/merge.ts, packages/core/src/errors/merge.ts, packages/core/src/errors/index.ts, packages/core/src/errors/code.ts, packages/core/test/unit/parameter/merge.spec.ts, packages/typeorm/test/unit/acceptance.spec.ts
merge/and/or methods are added to parameter collections; MergeError and FILTERS_NOT_FLAT support validation; mergeQueries combines query components with left priority.
Documentation and examples
.agents/*.md, README.MD, packages/docs/guide/*, packages/docs/.vitepress/config.mjs, packages/docs/getting-started/quick-start.md
Architecture/structure docs, README examples, and query guides describe the new API and query composition flow.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • tada5hi/rapiq#700: Refactors the same core filter/query type surfaces used by the new build and helper APIs.
  • tada5hi/rapiq#741: Introduces the AdapterError and FEATURE_UNSUPPORTED path used by the codec changes here.
  • tada5hi/rapiq#745: Overlaps with the parser/query flow changes away from QueryBuilder.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the core change set: typed build layer, condition helpers, and query merging in @rapiq/core.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/build-layer-and-ir-merge

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-parameter merge semantics, plus Filters.merge vs Filters.and/or split 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.

Comment thread packages/core/src/build/parameter/filters/module.ts Outdated
Comment thread packages/core/src/build/parameter/pagination/module.ts
tada5hi added 2 commits July 7, 2026 12:12
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
packages/core/src/parameter/filters/helpers/module.ts (1)

30-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce duplication across the 12 uniform-signature helpers.

eq/ne/lt/lte/gt/gte and the six startsWith-family helpers each differ only by which FilterFieldOperator constant 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 createStringHelper

Also 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 win

Build layer tree omits top-level files.

The build/ subtree only lists the parameter/ subdirectory, but the layer's own file list includes top-level build/module.ts, build/index.ts, build/types.ts, and build/utils.ts (the defineQuery composer, barrel, and shared build-input contract). The parameter/ entry above it does list its top-level merge.ts/module.ts files, so this is an inconsistent level of detail.

📝 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)
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."
🤖 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 win

Duplicate 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 mirrors Fields.merge per 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

📥 Commits

Reviewing files that changed from the base of the PR and between b0f9156 and 39dd0b3.

📒 Files selected for processing (57)
  • .agents/architecture.md
  • .agents/structure.md
  • README.MD
  • packages/codec-url-simple/test/unit/query.spec.ts
  • packages/core/src/build/index.ts
  • packages/core/src/build/module.ts
  • packages/core/src/build/parameter/fields/index.ts
  • packages/core/src/build/parameter/fields/module.ts
  • packages/core/src/build/parameter/fields/types.ts
  • packages/core/src/build/parameter/filters/index.ts
  • packages/core/src/build/parameter/filters/module.ts
  • packages/core/src/build/parameter/filters/types.ts
  • packages/core/src/build/parameter/index.ts
  • packages/core/src/build/parameter/pagination/index.ts
  • packages/core/src/build/parameter/pagination/module.ts
  • packages/core/src/build/parameter/pagination/types.ts
  • packages/core/src/build/parameter/relations/index.ts
  • packages/core/src/build/parameter/relations/module.ts
  • packages/core/src/build/parameter/relations/types.ts
  • packages/core/src/build/parameter/sorts/index.ts
  • packages/core/src/build/parameter/sorts/module.ts
  • packages/core/src/build/parameter/sorts/types.ts
  • packages/core/src/build/types.ts
  • packages/core/src/build/utils.ts
  • packages/core/src/errors/build.ts
  • packages/core/src/errors/code.ts
  • packages/core/src/errors/index.ts
  • packages/core/src/errors/merge.ts
  • packages/core/src/index.ts
  • packages/core/src/parameter/builder.ts
  • packages/core/src/parameter/fields/collection/module.ts
  • packages/core/src/parameter/fields/collection/types.ts
  • packages/core/src/parameter/filters/collection/module.ts
  • packages/core/src/parameter/filters/collection/types.ts
  • packages/core/src/parameter/filters/helpers/index.ts
  • packages/core/src/parameter/filters/helpers/module.ts
  • packages/core/src/parameter/filters/index.ts
  • packages/core/src/parameter/filters/record/check.ts
  • packages/core/src/parameter/filters/record/index.ts
  • packages/core/src/parameter/index.ts
  • packages/core/src/parameter/merge.ts
  • packages/core/src/parameter/pagination/pagination.ts
  • packages/core/src/parameter/pagination/types.ts
  • packages/core/src/parameter/relations/collection/module.ts
  • packages/core/src/parameter/relations/collection/types.ts
  • packages/core/src/parameter/sorts/collection/module.ts
  • packages/core/src/parameter/sorts/collection/types.ts
  • packages/core/test/unit/build/module.spec.ts
  • packages/core/test/unit/parameter/filters-helpers.spec.ts
  • packages/core/test/unit/parameter/merge.spec.ts
  • packages/docs/.vitepress/config.mjs
  • packages/docs/guide/build.md
  • packages/docs/guide/filters.md
  • packages/docs/guide/index.md
  • packages/docs/guide/merge.md
  • packages/docs/guide/query.md
  • packages/typeorm/test/unit/acceptance.spec.ts

Comment thread packages/core/src/build/parameter/filters/module.ts
Comment thread packages/core/src/build/parameter/relations/module.ts
Comment thread packages/core/src/build/parameter/sorts/module.ts
Comment thread packages/docs/guide/build.md Outdated
tada5hi added 3 commits July 7, 2026 12:48
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
@tada5hi

tada5hi commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/core/src/parameter/relations/collection/module.ts (1)

43-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract 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 value

Avoid mutating the caller-supplied options object.

Line 16 mutates message in place. If a caller passes a reused/shared BaseErrorOptions object, this constructor will silently overwrite its message property 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

📥 Commits

Reviewing files that changed from the base of the PR and between b0f9156 and 288c793.

📒 Files selected for processing (65)
  • .agents/architecture.md
  • .agents/structure.md
  • README.MD
  • packages/codec-url-simple/src/encoder/visitors/filters.ts
  • packages/codec-url-simple/test/unit/query.spec.ts
  • packages/core/src/build/index.ts
  • packages/core/src/build/module.ts
  • packages/core/src/build/parameter/fields/index.ts
  • packages/core/src/build/parameter/fields/module.ts
  • packages/core/src/build/parameter/fields/types.ts
  • packages/core/src/build/parameter/filters/index.ts
  • packages/core/src/build/parameter/filters/module.ts
  • packages/core/src/build/parameter/filters/types.ts
  • packages/core/src/build/parameter/index.ts
  • packages/core/src/build/parameter/pagination/index.ts
  • packages/core/src/build/parameter/pagination/module.ts
  • packages/core/src/build/parameter/pagination/types.ts
  • packages/core/src/build/parameter/relations/index.ts
  • packages/core/src/build/parameter/relations/module.ts
  • packages/core/src/build/parameter/relations/types.ts
  • packages/core/src/build/parameter/sorts/index.ts
  • packages/core/src/build/parameter/sorts/module.ts
  • packages/core/src/build/parameter/sorts/types.ts
  • packages/core/src/build/types.ts
  • packages/core/src/build/utils.ts
  • packages/core/src/errors/build.ts
  • packages/core/src/errors/code.ts
  • packages/core/src/errors/index.ts
  • packages/core/src/errors/merge.ts
  • packages/core/src/index.ts
  • packages/core/src/parameter/builder.ts
  • packages/core/src/parameter/fields/collection/module.ts
  • packages/core/src/parameter/fields/collection/types.ts
  • packages/core/src/parameter/filters/collection/module.ts
  • packages/core/src/parameter/filters/collection/types.ts
  • packages/core/src/parameter/filters/helpers/index.ts
  • packages/core/src/parameter/filters/helpers/module.ts
  • packages/core/src/parameter/filters/index.ts
  • packages/core/src/parameter/filters/record/check.ts
  • packages/core/src/parameter/filters/record/index.ts
  • packages/core/src/parameter/index.ts
  • packages/core/src/parameter/merge.ts
  • packages/core/src/parameter/pagination/pagination.ts
  • packages/core/src/parameter/pagination/types.ts
  • packages/core/src/parameter/relations/collection/module.ts
  • packages/core/src/parameter/relations/collection/types.ts
  • packages/core/src/parameter/sorts/collection/module.ts
  • packages/core/src/parameter/sorts/collection/types.ts
  • packages/core/src/parser/query.ts
  • packages/core/test/unit/build/module.spec.ts
  • packages/core/test/unit/parameter/filters-helpers.spec.ts
  • packages/core/test/unit/parameter/merge.spec.ts
  • packages/docs/.vitepress/config.mjs
  • packages/docs/getting-started/quick-start.md
  • packages/docs/guide/build.md
  • packages/docs/guide/fields.md
  • packages/docs/guide/filters.md
  • packages/docs/guide/index.md
  • packages/docs/guide/merge.md
  • packages/docs/guide/pagination.md
  • packages/docs/guide/query.md
  • packages/docs/guide/relations.md
  • packages/docs/guide/sort.md
  • packages/sql/test/unit/adapter.spec.ts
  • packages/typeorm/test/unit/acceptance.spec.ts
💤 Files with no reviewable changes (1)
  • packages/core/src/parameter/builder.ts

@tada5hi
tada5hi merged commit fd2fae7 into master Jul 7, 2026
7 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
@github-actions github-actions Bot mentioned this pull request Jul 15, 2026
@github-actions github-actions Bot mentioned this pull request Jul 19, 2026
@tada5hi
tada5hi deleted the feat/build-layer-and-ir-merge branch July 27, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants