Skip to content

feat: codec completion — round-trip subset law, expression url codec & registry - #748

Merged
tada5hi merged 6 commits into
masterfrom
feat/codec-completion
Jul 7, 2026
Merged

feat: codec completion — round-trip subset law, expression url codec & registry#748
tada5hi merged 6 commits into
masterfrom
feat/codec-completion

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Implements plan 007 (roadmap 000, M3): round-trip completeness for the URL codec family, plus the scope decided in the 2026-07-07 session (schema-aware encode, expression-dialect URL codec, in-band codec identity).

Session decisions (recorded in the plan file)

  1. ~ semantics keep the current v2 mapping (text~ → STARTS_WITH, ~text → ENDS_WITH, ~text~ → CONTAINS) — breaking vs v1, logged in the new migration guide.
  2. Round-trip law is scoped modulo scalar type normalization (untyped wire: '5'5, 'true'true); only semantics-changing serialization throws.
  3. Schema-aware encode() is in scope; failure policy mirrors the parsers (drop by default, schema throwOnFailure opts into throwing).
  4. @rapiq/codec-url-expression built now as a sibling package.
  5. Codec identity goes in-band (reverses the earlier out-of-band-only decision): reserved codec query param + registry dispatch, defaulting to simple when absent.

Changes

@rapiq/parser-simple

  • New shared filter-value wire grammar (parameter/filters/value.ts): parseFilterScalar, parseFilterValue, parseFilterWireValue, serializeFilterValue — the single source for scalar coercion and operator-marker parsing. The three diverged normalizeValue copies (simple parser, expression parser, codec encoder) are deleted.

@rapiq/parser-expression

  • Quoted values are no longer comma-split: eq(name, 'a,b') parses to the plain string 'a,b' (lists are separate args in this dialect). Previously this threw an untyped SyntaxError.

@rapiq/codec-url-simple

  • Subset law enforced pointwise: every emitted wire token is re-parsed and must decode back to the operator it was serialized from. encode now throws typed errors instead of silently changing semantics — OPERATOR_UNSUPPORTED for REGEX/MOD/EXISTS/ELEM_MATCH (previously silently encoded as equality), FEATURE_UNSUPPORTED for same-field duplicates (previously last-write-wins), comma/empty values, and operator-marker collisions (e.g. eq on 'foo~').
  • Schema-aware encoding: encode(query, { schema, strict }) (and per-parameter variants) validates by piping the output through the schema-bound decoder and re-encoding — parser-exact drop/throw/mapping/maxLimit semantics by construction. Parameters absent from the input are masked so schema defaults are not materialized onto the wire.
  • Fixed: a thrown encode no longer leaks serializer state into the next encode call.
  • Round-trip suite: decode(encode(q)) ≍ q across the operator matrix, typed failures outside the subset.

@rapiq/codec-url-expression (new)

  • URL codec for the expression dialect: nested and/or compounds cross the URL boundary in a single filter=and(eq(name,'John'),or(...)) param; the other four parameters share the simple codec's wire machinery. Wider expressible subset (nested compounds, same-field branches, comma strings) with its own typed-failure matrix (no grammar production, keyword field segments, coercing match text).

@rapiq/codec-url (new)

  • URLCodecRegistry: encoding stamps the reserved codec parameter, decoding dispatches on it. Unstamped payloads fall back to the registry default (plain clients keep working); an unregistered stamped codec throws the new typed CodecError (ErrorCode.CODEC_UNRESOLVABLE, core) rather than silently mis-decoding. External codecs implement the URLCodec shape; createURLCodecRegistry() bundles simple (default) + expression. Each codec package exports its identifier constant (URL_SIMPLE_CODEC, URL_EXPRESSION_CODEC) for out-of-band negotiation too.

Docs

  • integrations/url.md rewritten: round-trip guarantee, per-dialect typed-failure matrices, schema-aware encoding, expression dialect, codec registry.
  • New guide/migration.md logging v1 behavior changes as introduced (~ prefix position, expression quoted values, loud codec failures, strict mode, left-join default).
  • .agents/{structure,architecture}.md updated; plan 007 and the roadmap M3 codec rows marked done.

Breaking changes

  • encode throws typed errors for queries outside the dialect subset (previously silent flattening/dropping/overwriting).
  • Expression parser: quoted comma strings stay strings.
  • vs v1 (documented in the migration guide): ~text changes meaning from starts-with to ends-with.

Verification

All 8 packages build (nx), 240+ tests green including three new round-trip/schema suites, eslint clean, docs site builds.

Summary by CodeRabbit

  • New Features

    • Added a new URL query codec option for expression-style filters, alongside a registry that can route between codec formats.
    • Introduced reserved codec stamping in encoded URLs so payloads can be decoded with the right format automatically.
    • Added schema-aware encoding/decoding improvements, including support for stricter validation and better handling of defaults.
  • Bug Fixes

    • Improved filter handling to reduce ambiguous round-trips and surface unsupported cases as clear errors.
    • Prevented accidental duplication and unwanted default values in encoded output.
  • Documentation

    • Updated URL and migration docs to cover the new codec options and behavior changes.

tada5hi added 5 commits July 7, 2026 16:30
- extract shared filter-value codec (parseFilterScalar, parseFilterValue,
  parseFilterWireValue, serializeFilterValue) into @rapiq/parser-simple;
  the simple parser, expression parser and URL encoder now share one
  implementation (three diverged normalizeValue copies deleted)
- encoder: REGEX/MOD/EXISTS/ELEM_MATCH throw OPERATOR_UNSUPPORTED instead
  of silently encoding as equality; same-field duplicates, comma/empty
  values and operator-marker collisions (e.g. EQUAL on 'foo~') throw
  FEATURE_UNSUPPORTED — every emitted wire token is re-parsed and must
  decode back to the operator it was serialized from
- URLEncoder resets serializer state before every encode call; a thrown
  encode no longer leaks conditions into the next one
- expression parser: quoted values are no longer comma-split ('a,b' stays
  a plain string; lists are separate args in that dialect)
- add round-trip suite: decode(encode(q)) equals q modulo scalar type
  normalization within the dialect subset, typed failure outside it
…tics

encode(query, { schema, strict }) (and the per-parameter encode methods)
validate the emitted output by piping it through the schema-bound decoder
and re-encoding: disallowed keys are dropped by default, schema-level
throwOnFailure opts into throwing, mappings/clamps (maxLimit) apply — the
client sees exactly what the server-side decode would produce. Parameters
absent from the input query are masked out so schema defaults are not
materialized onto the wire. URLEncoder now optionally takes a
SchemaRegistry (mirroring URLDecoder) for named-schema resolution and
relation traversal.
New sibling package carrying nested and/or compounds across a URL
boundary in a single parameter: filter=and(eq(name,'John'),or(...)).
The other four parameters share the simple codec's wire format
(URLEncoder composes @rapiq/codec-url-simple; URLDecoder delegates to
ExpressionParser). The expressible subset is wider than the simple
dialect's — nested compounds, same-field branches and comma strings
round-trip — with typed FEATURE_UNSUPPORTED/OPERATOR_UNSUPPORTED
failures outside it (REGEX/MOD/EXISTS/ELEM_MATCH, keyword or
non-tokenizable field segments, coercing match text). Schema-aware
encode works like the simple codec; note the expression filters
dialect is precise: schema violations in filters always throw.
Each URL codec package now exports a stable identifier constant
(URL_SIMPLE_CODEC, URL_EXPRESSION_CODEC). The new @rapiq/codec-url
wrapper provides URLCodecRegistry: encoding through it stamps a
reserved 'codec' query parameter, decoding dispatches on it — an
unstamped payload falls back to the registry default (plain clients
keep working), a payload naming an unregistered codec throws the new
typed CodecError (ErrorCode.CODEC_UNRESOLVABLE, core) instead of
silently mis-decoding under another dialect. External codecs plug in
by implementing the URLCodec shape; createURLCodecRegistry() bundles
the simple (default) and expression dialects.
- rewrite integrations/url.md: round-trip guarantee + typed-failure
  matrix per dialect, schema-aware encoding, expression dialect and
  codec registry sections
- add guide/migration.md logging v1 behavior changes as introduced
  (~ prefix position, expression quoted values, loud codec failures,
  strict mode, left-join default) + sidebar entry
- update integrations overview with the two new packages
- update .agents/{structure,architecture}.md (package inventory,
  dependency layers, plan-007 codec rules) and mark plan 007 / the
  roadmap M3 codec rows as done
Copilot AI review requested due to automatic review settings July 7, 2026 16:33
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tada5hi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d607951-da1d-4e66-8534-72132bb4616b

📥 Commits

Reviewing files that changed from the base of the PR and between 9001540 and f30fabc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • packages/codec-url-expression/package.json
  • packages/codec-url-expression/src/decoder/module.ts
  • packages/codec-url-expression/src/encoder/module.ts
  • packages/codec-url-expression/test/unit/encoder-schema.spec.ts
  • packages/codec-url-expression/test/unit/roundtrip.spec.ts
  • packages/codec-url-simple/test/unit/roundtrip.spec.ts
  • packages/codec-url/src/module.ts
  • packages/codec-url/test/unit/registry.spec.ts
  • packages/parser-simple/src/parameter/filters/value.ts
📝 Walkthrough

Walkthrough

Introduces shared filter-value wire grammar helpers in parser-simple, reworks codec-url-simple's encoder for schema-aware round-tripping and duplicate-field detection, adds the new @rapiq/codec-url-expression package (expression dialect codec), adds the new @rapiq/codec-url package (registry dispatching via an in-band codec parameter, backed by a new CodecError), and updates architecture/migration docs.

Changes

URL Codec Dialects and Registry

Layer / File(s) Summary
CodecError type and error code
packages/core/src/errors/code.ts, packages/core/src/errors/codec.ts, packages/core/src/errors/index.ts
Adds CODEC_UNRESOLVABLE error code and CodecError class with a notResolvable factory.
Shared filter-value wire grammar
packages/parser-simple/src/parameter/filters/value.ts, .../index.ts, .../module.ts, packages/parser-expression/src/parameter/filters/module.ts
Adds parseFilterScalar/parseFilterValue/parseFilterWireValue/serializeFilterValue and wires both parser packages to use them.
codec-url-simple schema-aware encoder rework
packages/codec-url-simple/src/encoder/module.ts, .../visitors/module.ts, .../visitors/filters.ts, .../serializer/record.ts, .../constants.ts
Reworks URLEncoder for schema-aware decode/re-encode round-tripping, adds QueryParameterMask, visitor reset(), RecordSerializer.has(), and rewritten filter condition serialization with round-trip verification.
codec-url-simple tests
packages/codec-url-simple/test/**
Adds schemas/types and unit tests for schema-aware encoding and codec round-trip guarantees.
codec-url-expression package: decoder and encoder
packages/codec-url-expression/src/**
Adds the new package with URLDecoder/URLEncoder, filter-expression serialization, constants, and build config.
codec-url-expression tests
packages/codec-url-expression/test/**
Adds schemas/types and unit tests for schema-aware encoding, operator matrix, and round-trip guarantees.
codec-url registry package
packages/codec-url/src/**
Adds URLCodecRegistry, URLCodec contracts, CODEC_PARAMETER, and createURLCodecRegistry factory.
codec-url registry tests
packages/codec-url/test/**
Adds unit tests for stamped-codec dispatch, fallback, and error paths.
Architecture, structure, and integration docs
.agents/architecture.md, .agents/structure.md, packages/docs/guide/migration.md, packages/docs/integrations/*
Updates architecture/structure notes, adds a migration guide, and documents the expression dialect and codec registry.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant URLCodecRegistry
  participant URLEncoderSimple
  participant URLEncoderExpr
  participant URLDecoder
  Caller->>URLCodecRegistry: encode(query, {codec: 'url-expression'})
  URLCodecRegistry->>URLEncoderExpr: encoder.encode(query, options)
  URLEncoderExpr->>URLDecoder: decode(wire) for schema validation
  URLDecoder-->>URLEncoderExpr: validated query
  URLEncoderExpr-->>URLCodecRegistry: wire string
  URLCodecRegistry-->>Caller: codec=url-expression&wire string
  Caller->>URLCodecRegistry: decode(stamped input)
  URLCodecRegistry->>URLCodecRegistry: resolve codec from CODEC_PARAMETER
  URLCodecRegistry->>URLEncoderSimple: fallback decoder.decode when default
  URLEncoderSimple-->>URLCodecRegistry: IQuery
  URLCodecRegistry-->>Caller: IQuery
Loading

Possibly related PRs

  • tada5hi/rapiq#699: Both PRs modify the @rapiq/codec-url-simple URLEncoder/URLDecoder implementation, including serializer reset behavior and encode method signatures.
  • tada5hi/rapiq#727: Both PRs modify packages/codec-url-simple/src/encoder/visitors/filters.ts operator handling and round-trip serialization.
  • tada5hi/rapiq#739: Both PRs edit packages/parser-expression/src/parameter/filters/module.ts and related parser-simple filter value parsing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the PR and captures the main additions: round-trip codec rules, the expression URL codec, and the registry.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/codec-completion

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

Implements plan 007’s URL-codec “round-trip subset law” across the URL codec family by centralizing scalar/wire-value normalization, adding an expression-dialect URL codec, and introducing an in-band codec identity + dispatch registry. This strengthens correctness guarantees by making lossy/ambiguous encodes fail loudly with typed errors and aligns docs with the new behavior and breaking changes vs v1.

Changes:

  • Added shared filter wire-value grammar in @rapiq/parser-simple (scalar coercion + operator marker parsing + safe serialization) and reused it from both parsers/codecs.
  • Implemented @rapiq/codec-url-expression and @rapiq/codec-url (URLCodecRegistry + codec= stamping/dispatch).
  • Added extensive round-trip and schema-aware encoding test suites and updated docs (URL guide + migration log + navigation).

Reviewed changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/parser-simple/src/parameter/filters/value.ts New shared scalar coercion + simple-dialect wire token parsing/serialization utilities.
packages/parser-simple/src/parameter/filters/module.ts Simple filters parser now delegates wire parsing to shared parseFilterWireValue.
packages/parser-simple/src/parameter/filters/index.ts Re-export shared filter value helpers.
packages/parser-expression/src/parameter/filters/module.ts Expression parser now uses shared scalar coercion; quoted strings no longer comma-split.
packages/docs/integrations/url.md Documents subset law/typed failures, schema-aware encode, expression codec, and registry behavior.
packages/docs/integrations/index.md Lists new URL codec packages and registry.
packages/docs/guide/migration.md New running migration log for intentional v1→v2 behavior changes.
packages/docs/.vitepress/config.mjs Adds Migration section to docs navigation.
packages/core/src/errors/index.ts Exposes new codec error type from core error barrel.
packages/core/src/errors/codec.ts New CodecError with CODEC_UNRESOLVABLE helper.
packages/core/src/errors/code.ts Adds ErrorCode.CODEC_UNRESOLVABLE.
packages/codec-url/tsdown.config.ts Build config for new @rapiq/codec-url package.
packages/codec-url/tsconfig.json TS config for new package (incl. tests).
packages/codec-url/tsconfig.build.json Build-only TS config for new package.
packages/codec-url/test/vitest.config.ts Vitest config for registry package tests.
packages/codec-url/test/unit/registry.spec.ts Tests for codec stamping, dispatch, fallback, and unresolvable codec errors.
packages/codec-url/src/types.ts Defines URLCodec and encoder/decoder interfaces used by the registry.
packages/codec-url/src/module.ts Implements URLCodecRegistry encode/decode dispatch using reserved codec parameter.
packages/codec-url/src/index.ts Barrel exports for the registry package.
packages/codec-url/src/factory.ts createURLCodecRegistry() bundling simple + expression codecs.
packages/codec-url/src/constants.ts Defines reserved CODEC_PARAMETER = 'codec'.
packages/codec-url/package.json New package manifest for @rapiq/codec-url.
packages/codec-url-simple/test/unit/roundtrip.spec.ts New comprehensive simple-dialect round-trip + typed-failure matrix tests.
packages/codec-url-simple/test/unit/encoder-schema.spec.ts New tests for schema-aware encoding semantics (drop/throw/mapping/clamp/masking).
packages/codec-url-simple/test/data/type.ts Test types used by schema-aware encoding suite.
packages/codec-url-simple/test/data/schema.ts Test schema registry used by schema-aware encoding suite.
packages/codec-url-simple/src/encoder/visitors/module.ts Adds parameter mask support + explicit visitor reset for schema-aware re-encode pass.
packages/codec-url-simple/src/encoder/visitors/filters.ts Enforces subset law, duplicate-field rejection, and uses shared serialize/parse helpers.
packages/codec-url-simple/src/encoder/serializer/record.ts Adds has() to detect duplicate keys before overwriting.
packages/codec-url-simple/src/encoder/module.ts Adds schema-aware encode by decode+re-encode with parameter masking; fixes state leakage.
packages/codec-url-simple/src/constants.ts Adds stable codec identifier constant URL_SIMPLE_CODEC.
packages/codec-url-expression/tsdown.config.ts Build config for new @rapiq/codec-url-expression package.
packages/codec-url-expression/tsconfig.json TS config for new package (incl. tests).
packages/codec-url-expression/tsconfig.build.json Build-only TS config for new package.
packages/codec-url-expression/test/vitest.config.ts Vitest config for expression codec tests.
packages/codec-url-expression/test/unit/roundtrip.spec.ts Round-trip and typed-failure matrix tests for expression dialect URL codec.
packages/codec-url-expression/test/unit/encoder-schema.spec.ts Tests expression codec schema-aware encoding behavior (precise filters).
packages/codec-url-expression/test/data/type.ts Test types used by expression codec schema suite.
packages/codec-url-expression/test/data/schema.ts Test schema registry used by expression codec schema suite.
packages/codec-url-expression/src/index.ts Barrel exports for expression codec package.
packages/codec-url-expression/src/encoder/module.ts Expression-dialect encoder (filters as expression string; other params via simple codec).
packages/codec-url-expression/src/encoder/index.ts Barrel exports for expression encoder.
packages/codec-url-expression/src/encoder/filters.ts Serializes filters AST to expression string with keyword-safe field validation.
packages/codec-url-expression/src/decoder/module.ts Decoder mapping URL params to canonical parameters and delegating to expression parser.
packages/codec-url-expression/src/decoder/index.ts Barrel exports for expression decoder.
packages/codec-url-expression/src/constants.ts Adds stable codec identifier constant URL_EXPRESSION_CODEC.
packages/codec-url-expression/package.json New package manifest for @rapiq/codec-url-expression.
package-lock.json Adds workspace links for the new codec packages.
.agents/structure.md Updates package inventory/layers and adds new codec packages.
.agents/architecture.md Documents plan 007 codec rules, registry dispatch, and shared wire grammar placement.

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

Comment thread packages/parser-simple/src/parameter/filters/value.ts
Comment thread packages/codec-url/src/module.ts
Comment thread packages/codec-url-expression/src/encoder/module.ts
@tada5hi tada5hi changed the title feat: codec completion — round-trip subset law, expression url codec & registry (plan 007) feat: codec completion — round-trip subset law, expression url codec & registry Jul 7, 2026

@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: 3

🧹 Nitpick comments (4)
packages/codec-url/src/module.ts (2)

46-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent overwrite on duplicate register() calls.

Registering two codecs with the same name silently overwrites the earlier entry without warning; a later duplicate could also unexpectedly steal the default via asDefault. Consider guarding or documenting this as intentional override behavior.

🤖 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/codec-url/src/module.ts` around lines 46 - 52, The register() method
on URLCodecBase currently overwrites an existing codec with the same name
without any indication, and a duplicate can also unexpectedly replace the
default when asDefault is set. Update register(codec, asDefault) to either guard
against duplicate codec.name values or make the override behavior explicit in
the method’s handling of this.items and defaultName, so repeated registrations
in URLCodecBase are intentional and predictable.

85-108: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid double-parsing: pass the already-parsed object to the sub-decoder.

parse(input) computes parsed on Line 89, but Line 107 delegates using the original input (still a raw string on the string-input path), forcing the sub-decoder to re-parse the same query string with qs a second time.

♻️ Proposed fix to avoid re-parsing
         const codec = this.resolve(name);

-        return codec.decoder.decode(input, options);
+        return codec.decoder.decode(parsed, options);
🤖 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/codec-url/src/module.ts` around lines 85 - 108, The decode method in
module.ts is parsing the input twice on the string path because it resolves the
codec from parsed but then calls codec.decoder.decode with the original input.
Update decode to pass the already-parsed object through to the sub-decoder after
resolve(name), while preserving the existing object-literal path and the
CODEC_PARAMETER checks, so the decoder chain uses the parsed value instead of
re-running parse/qs.
packages/codec-url-expression/src/encoder/module.ts (1)

26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Possible duplicate QueryParameterMask type across packages.

Per the stack context, codec-url-simple's encoder rework also "adds QueryParameterMask and visitor reset," and this type here has an identical shape/purpose (mask flags for fields/filters/pagination/relations/sorts). Consider extracting this to a shared location (e.g. @rapiq/core) to avoid drift between the two dialect codecs as the schema-aware encode contract evolves.

🤖 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/codec-url-expression/src/encoder/module.ts` around lines 26 - 32,
The QueryParameterMask type appears duplicated with the same flag set used by
the codec-url-simple encoder, so move this shared mask shape into a common home
and reuse it from both encoder modules. Update the encoder in module.ts to
import the shared type instead of defining a local copy, and make sure any
references in the codec-url-simple visitor reset and related encoding contract
point to the same symbol so the two codecs stay aligned as the mask evolves.
packages/codec-url-simple/src/encoder/module.ts (1)

74-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared schema-aware round-trip pattern.

encodeFields, encodeFilters, encodePagination, encodeRelations, and encodeSort repeat the exact same shape (reset → serialize → schema-aware guard → decodeX → null guard → reset → re-emit). Extracting a small helper parameterized by the visit and decode callbacks would remove five near-identical blocks and keep future changes to the round-trip flow in one place.

private encodeParameter<I, D>(
    input: I,
    options: ParseParameterOptions,
    visit: (v: I) => ISerializer<string | null>,
    decode: (encoded: string, o: ParseParameterOptions) => D | null,
): string | null {
    this.visitor.reset();
    const encoded = this.runSerializer(visit(input));
    if (encoded === null || !this.isSchemaAware(options)) {
        return encoded;
    }
    const decoded = decode(encoded, options);
    if (!decoded) {
        return null;
    }
    this.visitor.reset();
    return this.runSerializer(visit(decoded as unknown as I));
}
🤖 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/codec-url-simple/src/encoder/module.ts` around lines 74 - 174, The
schema-aware round-trip logic is duplicated across encodeFields, encodeFilters,
encodePagination, encodeRelations, and encodeSort in the encoder module. Extract
this repeated reset/serialize/schema guard/decode/null-check/re-emit flow into a
shared helper on the same class, parameterized by the visit and decode
callbacks, and update each encodeX method to delegate to it so future changes
only need to be made once.
🤖 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/codec-url-expression/package.json`:
- Around line 18-31: The package manifest is missing a direct runtime dependency
for `@rapiq/parser-simple`, even though src/encoder/filters.ts imports it
directly. Add `@rapiq/parser-simple` to the dependencies section of the manifest,
and if this package is also meant to be consumable by downstream projects,
mirror it in peerDependencies/devDependencies only as appropriate for the
existing dependency pattern used by the other `@rapiq` packages.

In `@packages/codec-url-expression/src/decoder/module.ts`:
- Around line 105-119: The decodeFilters() branch in ModuleDecoder is treating
empty string values as missing input, which causes `?filter=` to fall through to
the defaults path instead of raising the parser error. Update decodeFilters() to
check the URLParameter.FILTERS property with isPropertySet, matching decode(),
so that empty-string values are passed through to this.filters.parse(...) and
only truly absent values use undefined. Keep the logic within
ModuleDecoder.decodeFilters() and preserve the existing parse(options) flow.

In `@packages/parser-simple/src/parameter/filters/value.ts`:
- Around line 74-88: `parseFilterValue()` is incorrectly dropping `false` when
it normalizes array inputs, which breaks round-tripping for `IN`/`NOT_IN`
filters. Update the array-handling branch in `parseFilterValue` so the final
filter keeps `false` values instead of removing them, while still excluding only
truly empty/undefined entries; use the existing `serializeFilterValue()`
behavior as the reference for supported scalar values.

---

Nitpick comments:
In `@packages/codec-url-expression/src/encoder/module.ts`:
- Around line 26-32: The QueryParameterMask type appears duplicated with the
same flag set used by the codec-url-simple encoder, so move this shared mask
shape into a common home and reuse it from both encoder modules. Update the
encoder in module.ts to import the shared type instead of defining a local copy,
and make sure any references in the codec-url-simple visitor reset and related
encoding contract point to the same symbol so the two codecs stay aligned as the
mask evolves.

In `@packages/codec-url-simple/src/encoder/module.ts`:
- Around line 74-174: The schema-aware round-trip logic is duplicated across
encodeFields, encodeFilters, encodePagination, encodeRelations, and encodeSort
in the encoder module. Extract this repeated reset/serialize/schema
guard/decode/null-check/re-emit flow into a shared helper on the same class,
parameterized by the visit and decode callbacks, and update each encodeX method
to delegate to it so future changes only need to be made once.

In `@packages/codec-url/src/module.ts`:
- Around line 46-52: The register() method on URLCodecBase currently overwrites
an existing codec with the same name without any indication, and a duplicate can
also unexpectedly replace the default when asDefault is set. Update
register(codec, asDefault) to either guard against duplicate codec.name values
or make the override behavior explicit in the method’s handling of this.items
and defaultName, so repeated registrations in URLCodecBase are intentional and
predictable.
- Around line 85-108: The decode method in module.ts is parsing the input twice
on the string path because it resolves the codec from parsed but then calls
codec.decoder.decode with the original input. Update decode to pass the
already-parsed object through to the sub-decoder after resolve(name), while
preserving the existing object-literal path and the CODEC_PARAMETER checks, so
the decoder chain uses the parsed value instead of re-running parse/qs.
🪄 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: 7e8acb59-bd9d-4777-9eae-2cb82cdd044d

📥 Commits

Reviewing files that changed from the base of the PR and between fd2fae7 and 9001540.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (49)
  • .agents/architecture.md
  • .agents/structure.md
  • packages/codec-url-expression/package.json
  • packages/codec-url-expression/src/constants.ts
  • packages/codec-url-expression/src/decoder/index.ts
  • packages/codec-url-expression/src/decoder/module.ts
  • packages/codec-url-expression/src/encoder/filters.ts
  • packages/codec-url-expression/src/encoder/index.ts
  • packages/codec-url-expression/src/encoder/module.ts
  • packages/codec-url-expression/src/index.ts
  • packages/codec-url-expression/test/data/schema.ts
  • packages/codec-url-expression/test/data/type.ts
  • packages/codec-url-expression/test/unit/encoder-schema.spec.ts
  • packages/codec-url-expression/test/unit/roundtrip.spec.ts
  • packages/codec-url-expression/test/vitest.config.ts
  • packages/codec-url-expression/tsconfig.build.json
  • packages/codec-url-expression/tsconfig.json
  • packages/codec-url-expression/tsdown.config.ts
  • packages/codec-url-simple/src/constants.ts
  • packages/codec-url-simple/src/encoder/module.ts
  • packages/codec-url-simple/src/encoder/serializer/record.ts
  • packages/codec-url-simple/src/encoder/visitors/filters.ts
  • packages/codec-url-simple/src/encoder/visitors/module.ts
  • packages/codec-url-simple/test/data/schema.ts
  • packages/codec-url-simple/test/data/type.ts
  • packages/codec-url-simple/test/unit/encoder-schema.spec.ts
  • packages/codec-url-simple/test/unit/roundtrip.spec.ts
  • packages/codec-url/package.json
  • packages/codec-url/src/constants.ts
  • packages/codec-url/src/factory.ts
  • packages/codec-url/src/index.ts
  • packages/codec-url/src/module.ts
  • packages/codec-url/src/types.ts
  • packages/codec-url/test/unit/registry.spec.ts
  • packages/codec-url/test/vitest.config.ts
  • packages/codec-url/tsconfig.build.json
  • packages/codec-url/tsconfig.json
  • packages/codec-url/tsdown.config.ts
  • packages/core/src/errors/code.ts
  • packages/core/src/errors/codec.ts
  • packages/core/src/errors/index.ts
  • packages/docs/.vitepress/config.mjs
  • packages/docs/guide/migration.md
  • packages/docs/integrations/index.md
  • packages/docs/integrations/url.md
  • packages/parser-expression/src/parameter/filters/module.ts
  • packages/parser-simple/src/parameter/filters/index.ts
  • packages/parser-simple/src/parameter/filters/module.ts
  • packages/parser-simple/src/parameter/filters/value.ts

Comment thread packages/codec-url-expression/package.json
Comment thread packages/codec-url-expression/src/decoder/module.ts
Comment thread packages/parser-simple/src/parameter/filters/value.ts
- parser-simple: keep boolean false when normalizing array filter
  values — in(flag, [true, false]) no longer decodes as [true]
- codec-url: strip the reserved codec parameter before delegating to
  the dispatched decoder; external codecs never see it
- codec-url-expression: pass the SchemaRegistry to the embedded simple
  encoder so named schemas resolve in per-parameter encodes; declare
  the direct @rapiq/parser-simple dependency; decodeFilters treats
  'filter=' as present-but-empty (syntax error) instead of falling
  back to schema defaults, matching decode()
@tada5hi
tada5hi merged commit 42fc558 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/codec-completion 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