Skip to content

feat(core)!: shared parser orchestration, unified defaults, codec boundary decode - #745

Merged
tada5hi merged 2 commits into
masterfrom
feat/parser-orchestration
Jul 6, 2026
Merged

feat(core)!: shared parser orchestration, unified defaults, codec boundary decode#745
tada5hi merged 2 commits into
masterfrom
feat/parser-orchestration

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Implements plan 006 (parser orchestration, roadmap M2), following the IR-centric layering now recorded in .agents/architecture.md: parsers transform dialect input to the IR (Query AST) and are transport-agnostic; codecs own the wire format end to end.

Shared orchestration

  • New BaseQueryParser in @rapiq/core (src/parser/query.ts) owns the single composite parse() body; SimpleParser / ExpressionParser are now sub-parser wiring only (their duplicated ~180-line bodies are gone).
  • New IQueryParameterParser contract and ParseQueryOptions type in core; the identical private types.ts files of both parser packages are deleted.
  • Parsers read only the canonical Parameter keys — no transport naming in core or the parser packages.

Codec as boundary adapter

  • URLParameter stays exclusively in @rapiq/codec-url-simple.
  • URLDecoder.decode(input, options?) now accepts a raw query string or a pre-parsed query object (express req.query), maps the JSON-API wire names (filter, page, include, …) to canonical parameters and delegates to a schema-aware SimpleParser — registry via constructor, schema via options. Its duplicated five-block decode body is gone too.
  • Per-parameter decode* methods now forward { schema } options.

Unified defaults

  • Every sub-parser is always invoked; absent input is not a failure and applies schema defaults. Fixes: pagination.maxLimit not applied when page absent, allowed-only fields schemas not projected (v1 parity), filters/sort defaults skipped for non-object input, throwOnFailure tripping on absent parameters.
  • Relations allow-list is now enforced in full-query parsing (PR feat(core)!: ResolutionScope resolution engine + public API cleanup #739 review addendum); relation-path gating for other parameters still only engages when the client sent a relations parameter.
  • Sort defaults no longer get the schema-name prefix — defaults come out in the same shape as input-derived keys.

Expression dialect

  • not() wraps match expressions via the existing NOT_CONTAINS / NOT_STARTS_WITH / NOT_ENDS_WITH operators.
  • Raw Error throws became FiltersParseError.syntaxInvalid(...) with new ErrorCode.SYNTAX_INVALID.
  • Tokenizer fix: keywords no longer match as prefixes inside identifiers (a field named order previously tokenized as or + der and failed).

Docs & tests

  • packages/docs/integrations/{simple,expression,url}.md updated: transport-agnostic parsers, req.query decoding via the codec, defaults behavior, not() / error semantics. The outdated “decode does not validate” warning is replaced by the schema-aware decoder.
  • New composite-parse specs (defaults, relations enforcement) in both parser packages; new decoder.spec.ts in the codec (string input, object input, string/object equivalence, schema validation); expression specs for negated matches, keyword-prefixed fields, typed syntax errors.
  • .agents/architecture.md gains a “Layering Principles (IR-centric)” section; plan 006 marked done with the revised item-1 resolution.

Breaking changes

  • Schema defaults now apply in composite parsing even when a parameter is absent (e.g. maxLimit echoes into Query.pagination, allowed-only field schemas produce an explicit projection).
  • URLDecoder validates against a schema when constructed with a SchemaRegistry and given a schema option (previously it never validated).

Verification

  • nx run-many -t build — 7 projects green
  • nx run-many -t test — 385 tests across 6 projects green
  • eslint clean on changed packages; docs site builds
  • grep -rn 'todo: parse parameter' packages/ — empty (plan acceptance criterion)

Summary by CodeRabbit

  • New Features
    • Schema-aware defaults now apply for fields, filters, pagination, relations, and sort during query parsing.
    • URL decoding now accepts either a raw query string or an already-parsed query object.
    • Expression filtering expands not(...) support for contains, startsWith, and endsWith.
  • Bug Fixes
    • Improved handling of absent input so optional query parts don’t fail unexpectedly.
    • Malformed filter syntax now reports clearer syntax errors via dedicated error codes.
  • Documentation
    • Updated URL, simple, and expression integration docs to reflect schema-aware defaults and improved error details.
  • Tests
    • Added/expanded unit coverage for defaults, URL decoding, and syntax/error cases.

Copilot AI review requested due to automatic review settings July 6, 2026 11:10
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new BaseQueryParser and related query parsing option/types are added to @rapiq/core, along with ErrorCode.SYNTAX_INVALID and ParseError.syntaxInvalid. URLDecoder, ExpressionParser, and SimpleParser are updated to use the shared orchestration, with schema-aware defaults and updated tests and docs.

Changes

Core query parsing and error surface

Layer / File(s) Summary
Core error code additions
packages/core/src/constants.ts, packages/core/src/errors/code.ts, packages/core/src/errors/parse.ts
Adds ErrorCode.SYNTAX_INVALID and ParseError.syntaxInvalid(details?), plus a formatting-only constants change.
BaseQueryParser orchestration and types in core
packages/core/src/parser/query.ts, packages/core/src/parser/types.ts, packages/core/src/parser/index.ts
Adds BaseQueryParser, ParseQueryOptions, IQueryParameterParser, and the parser index re-export.
codec-url-simple decoder migration
packages/codec-url-simple/src/constants.ts, packages/codec-url-simple/src/decoder/module.ts, packages/codec-url-simple/src/encoder/visitors/fields.ts, packages/codec-url-simple/test/unit/decoder.spec.ts
Updates URLDecoder to accept schema-aware options, map wire keys to canonical parameters, and decode through SimpleParser, with matching tests and comment/import formatting updates.
parser-expression migration and filters defaults
packages/parser-expression/src/module.ts, packages/parser-expression/src/parameter/filters/module.ts, packages/parser-expression/src/types.ts, packages/parser-expression/test/unit/parser/*
Moves ExpressionParser onto BaseQueryParser, updates expression filter parsing/error handling and schema defaults, removes the local ParseOptions type, and adds coverage for syntax errors, negation, and defaults.
parser-simple migration and sub-parser defaults
packages/parser-simple/src/module.ts, packages/parser-simple/src/parameter/filters/module.ts, packages/parser-simple/src/parameter/pagination/module.ts, packages/parser-simple/src/parameter/sorts/module.ts, packages/parser-simple/src/types.ts, packages/parser-simple/test/unit/parser/parser.spec.ts
Moves SimpleParser onto BaseQueryParser, adjusts absent-input handling in filters/pagination/sort for schema defaults, removes the local ParseOptions type, and extends parser tests.
Documentation updates
.agents/architecture.md, packages/docs/integrations/expression.md, packages/docs/integrations/simple.md, packages/docs/integrations/url.md
Updates IR layering guidance, expression rules/errors, simple parser defaults, and URL decoder examples/contract text.

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

Possibly related PRs

  • tada5hi/rapiq#699: Related URLDecoder work in the same decoder module.
  • tada5hi/rapiq#700: Related public URLDecoder signature changes in the same API surface.
  • tada5hi/rapiq#739: Related parser orchestration changes that align with the shared query parsing refactor.
🚥 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 accurately summarizes the main changes: shared parser orchestration, unified defaults, and codec-boundary decoding.
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/parser-orchestration

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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 centralizes query parsing orchestration in @rapiq/core via a new BaseQueryParser, adds support for JSON-API wire parameter names (filter, page, include) alongside canonical keys, and makes schema defaults consistently apply during full-query parsing (even when parameters are absent). It also tightens the expression dialect with typed syntax errors, correct keyword tokenization, and not() support for match operators, while updating docs/tests and moving URLParameter into core.

Changes:

  • Introduces BaseQueryParser + IQueryParameterParser / ParseQueryOptions in core, removing duplicated composite parse() logic and per-package types.ts.
  • Adds URL-parameter wire-name acceptance with canonical-key precedence; migrates URLParameter into @rapiq/core and updates codec-url-simple to import it from core.
  • Applies schema defaults during composite parsing and updates expression parsing semantics (typed SYNTAX_INVALID, not() for match ops, tokenizer fix).

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/parser-simple/test/unit/parser/parser.spec.ts Adds composite-parse tests for wire names, precedence, defaults, and relations allow-list enforcement.
packages/parser-simple/src/types.ts Removes package-local ParseOptions type (now replaced by core query options types).
packages/parser-simple/src/parameter/sorts/module.ts Aligns sort-default output keys to match input-derived shape (no implicit schema-name prefixing).
packages/parser-simple/src/parameter/pagination/module.ts Treats absent pagination input as non-failure while still applying schema constraints (e.g. maxLimit).
packages/parser-simple/src/parameter/filters/module.ts Treats absent filters input as non-failure so schema defaults can apply.
packages/parser-simple/src/module.ts Switches SimpleParser to extend BaseQueryParser and removes duplicated orchestration.
packages/parser-expression/test/unit/parser/parser.spec.ts Adds composite-parse tests for expression parser (wire names, precedence, allow-list enforcement, defaults).
packages/parser-expression/test/unit/parser/filters.spec.ts Adds tests for not() match negation, keyword-prefixed identifiers, typed syntax errors, and defaults-on-absent.
packages/parser-expression/src/types.ts Removes package-local ParseOptions type (now replaced by core query options types).
packages/parser-expression/src/parameter/filters/module.ts Adds defaults-on-absent behavior, typed syntax errors, tokenizer fix, and not() support for match operators.
packages/parser-expression/src/module.ts Switches ExpressionParser to extend BaseQueryParser and removes duplicated orchestration.
packages/docs/integrations/simple.md Documents wire names and default-application behavior for full-query parsing.
packages/docs/integrations/expression.md Updates expression dialect docs (wire names, not() match negation, SYNTAX_INVALID semantics).
packages/core/src/parser/types.ts Adds ParseQueryOptions and IQueryParameterParser contract in core.
packages/core/src/parser/query.ts Introduces shared composite query parse orchestration with canonical/wire parameter lookup and relations gating.
packages/core/src/parser/index.ts Re-exports the new query parser orchestrator.
packages/core/src/errors/parse.ts Adds ParseError.syntaxInvalid() helper producing ErrorCode.SYNTAX_INVALID.
packages/core/src/errors/code.ts Adds ErrorCode.SYNTAX_INVALID.
packages/core/src/constants.ts Adds URLParameter enum (JSON-API wire names) in core.
packages/codec-url-simple/src/index.ts Stops exporting codec-local constants (URLParameter now comes from core).
packages/codec-url-simple/src/encoder/visitors/sort.ts Switches URLParameter import to @rapiq/core for sort serialization.
packages/codec-url-simple/src/encoder/visitors/relations.ts Switches URLParameter import to @rapiq/core for relations serialization.
packages/codec-url-simple/src/encoder/visitors/pagination.ts Switches URLParameter import to @rapiq/core for pagination serialization.
packages/codec-url-simple/src/encoder/visitors/filters.ts Switches URLParameter import to @rapiq/core for filters serialization.
packages/codec-url-simple/src/encoder/visitors/fields.ts Switches URLParameter import to @rapiq/core for fields serialization.
packages/codec-url-simple/src/encoder/serializer/module.ts Switches URLParameter import to @rapiq/core for serializer wiring.
packages/codec-url-simple/src/decoder/module.ts Switches URLParameter import to @rapiq/core for decode mapping.
packages/codec-url-simple/src/constants.ts Deletes codec-local URLParameter enum (moved to core).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/parser/query.ts Outdated
Comment thread packages/parser-expression/src/parameter/filters/module.ts Outdated
Comment thread packages/codec-url-simple/src/encoder/visitors/sort.ts
Comment on lines 12 to 15
FilterFieldOperator,
Filters,
Filters,
URLParameter,
} from '@rapiq/core';
Comment on lines 15 to 19
import {
DEFAULT_ID,
parseKey,
URLParameter,
parseKey,
} from '@rapiq/core';
@tada5hi

tada5hi commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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 (1)
packages/parser-simple/src/parameter/pagination/module.ts (1)

38-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate "absent vs present-invalid" check across parsers.

The same typeof input !== 'undefined' && input !== null && throwOnFailure guard is now duplicated verbatim in both SimpleFiltersParser.run (packages/parser-simple/src/parameter/filters/module.ts, Lines 83-87) and here. Consider hoisting an isInputAbsent(input) helper (e.g. onto BaseParser in @rapiq/core) to avoid drift if the semantics need to change again.

Also applies to: 81-92

🤖 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/parser-simple/src/parameter/pagination/module.ts` around lines 38 -
50, The present-vs-absent input guard is duplicated in pagination parsing and
should be centralized to prevent drift. Extract the shared `typeof input !==
'undefined' && input !== null` check into a reusable helper such as
`isInputAbsent(input)` on `BaseParser` in `@rapiq/core`, then update
`PaginationParser.run` and `SimpleFiltersParser.run` to use that helper before
deciding whether to throw `PaginationParseError.inputInvalid()` or continue with
`finalizePagination`.
🤖 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/parser-simple/src/parameter/pagination/module.ts`:
- Around line 38-50: The present-vs-absent input guard is duplicated in
pagination parsing and should be centralized to prevent drift. Extract the
shared `typeof input !== 'undefined' && input !== null` check into a reusable
helper such as `isInputAbsent(input)` on `BaseParser` in `@rapiq/core`, then
update `PaginationParser.run` and `SimpleFiltersParser.run` to use that helper
before deciding whether to throw `PaginationParseError.inputInvalid()` or
continue with `finalizePagination`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e96edf8f-bd04-4e33-805b-8a4c63ec3167

📥 Commits

Reviewing files that changed from the base of the PR and between f7c33c0 and 454447c.

📒 Files selected for processing (28)
  • packages/codec-url-simple/src/constants.ts
  • packages/codec-url-simple/src/decoder/module.ts
  • packages/codec-url-simple/src/encoder/serializer/module.ts
  • packages/codec-url-simple/src/encoder/visitors/fields.ts
  • packages/codec-url-simple/src/encoder/visitors/filters.ts
  • packages/codec-url-simple/src/encoder/visitors/pagination.ts
  • packages/codec-url-simple/src/encoder/visitors/relations.ts
  • packages/codec-url-simple/src/encoder/visitors/sort.ts
  • packages/codec-url-simple/src/index.ts
  • packages/core/src/constants.ts
  • packages/core/src/errors/code.ts
  • packages/core/src/errors/parse.ts
  • packages/core/src/parser/index.ts
  • packages/core/src/parser/query.ts
  • packages/core/src/parser/types.ts
  • packages/docs/integrations/expression.md
  • packages/docs/integrations/simple.md
  • packages/parser-expression/src/module.ts
  • packages/parser-expression/src/parameter/filters/module.ts
  • packages/parser-expression/src/types.ts
  • packages/parser-expression/test/unit/parser/filters.spec.ts
  • packages/parser-expression/test/unit/parser/parser.spec.ts
  • packages/parser-simple/src/module.ts
  • packages/parser-simple/src/parameter/filters/module.ts
  • packages/parser-simple/src/parameter/pagination/module.ts
  • packages/parser-simple/src/parameter/sorts/module.ts
  • packages/parser-simple/src/types.ts
  • packages/parser-simple/test/unit/parser/parser.spec.ts
💤 Files with no reviewable changes (4)
  • packages/codec-url-simple/src/constants.ts
  • packages/parser-expression/src/types.ts
  • packages/codec-url-simple/src/index.ts
  • packages/parser-simple/src/types.ts

…ndary decode

BaseQueryParser in @rapiq/core owns the composite parse() body;
SimpleParser/ExpressionParser become sub-parser wiring. Parsers are
transport-agnostic and read only the canonical Parameter keys — the
JSON-API wire names (URLParameter) stay codec-only. URLDecoder is the
boundary adapter: decode() now accepts a raw query string or a
pre-parsed query object (express req.query), maps the wire names and
delegates to a schema-aware SimpleParser (registry via constructor,
schema via options).

Defaults are owned by the sub-parsers: every sub-parser is always
invoked and absent input is not a failure. This applies
pagination.maxLimit, fields allowed/default, filters.default and
sort.default when the parameter is absent, enforces the relations
allow-list in full-query parsing, and drops the schema-name prefix from
sort defaults.

Expression dialect: not() now wraps match expressions via the NOT_*
operators, raw Errors became FiltersParseError.syntaxInvalid
(ErrorCode.SYNTAX_INVALID), and the tokenizer no longer splits
identifiers starting with a keyword (e.g. "order").

BREAKING CHANGE: schema defaults now apply in composite parsing even
when a parameter is absent from the input; URLDecoder performs schema
validation when constructed with a registry.
@tada5hi
tada5hi force-pushed the feat/parser-orchestration branch from 454447c to fa4a3c6 Compare July 6, 2026 13:44
@tada5hi tada5hi changed the title feat(core)!: shared parser orchestration, url-parameter keys, unified defaults feat(core)!: shared parser orchestration, unified defaults, codec boundary decode Jul 6, 2026
…n filter input

Review feedback (PR #745): the OPTIONS type parameter on BaseQueryParser
was never consumed by parse(); an empty filter expression string
(?filter=) now surfaces a typed syntax error again instead of silently
applying schema defaults.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/parser-expression/src/parameter/filters/module.ts (1)

431-442: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Leftover raw SyntaxError undermines the typed-error refactor.

parseExpressionValue (changed at line 438) and parseMatchExpression (line 354) both delegate to normalizeValue, which still throws a raw new SyntaxError('Value can not be normalized.') at line 533. This is inconsistent with the rest of the file's conversion to FiltersParseError.syntaxInvalid(...)/keyValueInvalid(...), meaning callers expecting typed FiltersParseError instances can still receive an untyped SyntaxError from this codepath.

Suggested fix (outside selected range, applies to line 533)
-        throw new SyntaxError('Value can not be normalized.');
+        throw FiltersParseError.syntaxInvalid('Value can not be normalized.');
🤖 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/parser-expression/src/parameter/filters/module.ts` around lines 431
- 442, The typed-error refactor is incomplete because normalizeValue still
throws a raw SyntaxError, so callers of parseExpressionValue and
parseMatchExpression can receive an untyped exception. Update normalizeValue to
throw FiltersParseError.syntaxInvalid or FiltersParseError.keyValueInvalid
instead of new SyntaxError, keeping the error shape consistent with the rest of
module.ts and preserving the same failure context for token/value normalization.
🤖 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.

Outside diff comments:
In `@packages/parser-expression/src/parameter/filters/module.ts`:
- Around line 431-442: The typed-error refactor is incomplete because
normalizeValue still throws a raw SyntaxError, so callers of
parseExpressionValue and parseMatchExpression can receive an untyped exception.
Update normalizeValue to throw FiltersParseError.syntaxInvalid or
FiltersParseError.keyValueInvalid instead of new SyntaxError, keeping the error
shape consistent with the rest of module.ts and preserving the same failure
context for token/value normalization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26f636ea-609f-46db-ac40-99f22ea2f05e

📥 Commits

Reviewing files that changed from the base of the PR and between fa4a3c6 and 34672ac.

📒 Files selected for processing (4)
  • packages/codec-url-simple/src/encoder/visitors/sort.ts
  • packages/core/src/parser/query.ts
  • packages/parser-expression/src/parameter/filters/module.ts
  • packages/parser-expression/test/unit/parser/filters.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/codec-url-simple/src/encoder/visitors/sort.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/parser-expression/test/unit/parser/filters.spec.ts
  • packages/core/src/parser/query.ts

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