feat: codec completion — round-trip subset law, expression url codec & registry - #748
Conversation
- 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
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (9)
📝 WalkthroughWalkthroughIntroduces 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 ChangesURL Codec Dialects and 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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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-expressionand@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.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/codec-url/src/module.ts (2)
46-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSilent overwrite on duplicate
register()calls.Registering two codecs with the same
namesilently overwrites the earlier entry without warning; a later duplicate could also unexpectedly steal the default viaasDefault. 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 winAvoid double-parsing: pass the already-parsed object to the sub-decoder.
parse(input)computesparsedon Line 89, but Line 107 delegates using the originalinput(still a raw string on the string-input path), forcing the sub-decoder to re-parse the same query string withqsa 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 tradeoffPossible duplicate
QueryParameterMasktype across packages.Per the stack context,
codec-url-simple's encoder rework also "addsQueryParameterMaskand 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 winConsider extracting the shared schema-aware round-trip pattern.
encodeFields,encodeFilters,encodePagination,encodeRelations, andencodeSortrepeat 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (49)
.agents/architecture.md.agents/structure.mdpackages/codec-url-expression/package.jsonpackages/codec-url-expression/src/constants.tspackages/codec-url-expression/src/decoder/index.tspackages/codec-url-expression/src/decoder/module.tspackages/codec-url-expression/src/encoder/filters.tspackages/codec-url-expression/src/encoder/index.tspackages/codec-url-expression/src/encoder/module.tspackages/codec-url-expression/src/index.tspackages/codec-url-expression/test/data/schema.tspackages/codec-url-expression/test/data/type.tspackages/codec-url-expression/test/unit/encoder-schema.spec.tspackages/codec-url-expression/test/unit/roundtrip.spec.tspackages/codec-url-expression/test/vitest.config.tspackages/codec-url-expression/tsconfig.build.jsonpackages/codec-url-expression/tsconfig.jsonpackages/codec-url-expression/tsdown.config.tspackages/codec-url-simple/src/constants.tspackages/codec-url-simple/src/encoder/module.tspackages/codec-url-simple/src/encoder/serializer/record.tspackages/codec-url-simple/src/encoder/visitors/filters.tspackages/codec-url-simple/src/encoder/visitors/module.tspackages/codec-url-simple/test/data/schema.tspackages/codec-url-simple/test/data/type.tspackages/codec-url-simple/test/unit/encoder-schema.spec.tspackages/codec-url-simple/test/unit/roundtrip.spec.tspackages/codec-url/package.jsonpackages/codec-url/src/constants.tspackages/codec-url/src/factory.tspackages/codec-url/src/index.tspackages/codec-url/src/module.tspackages/codec-url/src/types.tspackages/codec-url/test/unit/registry.spec.tspackages/codec-url/test/vitest.config.tspackages/codec-url/tsconfig.build.jsonpackages/codec-url/tsconfig.jsonpackages/codec-url/tsdown.config.tspackages/core/src/errors/code.tspackages/core/src/errors/codec.tspackages/core/src/errors/index.tspackages/docs/.vitepress/config.mjspackages/docs/guide/migration.mdpackages/docs/integrations/index.mdpackages/docs/integrations/url.mdpackages/parser-expression/src/parameter/filters/module.tspackages/parser-simple/src/parameter/filters/index.tspackages/parser-simple/src/parameter/filters/module.tspackages/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()
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)
~semantics keep the current v2 mapping (text~→ STARTS_WITH,~text→ ENDS_WITH,~text~→ CONTAINS) — breaking vs v1, logged in the new migration guide.'5'→5,'true'→true); only semantics-changing serialization throws.encode()is in scope; failure policy mirrors the parsers (drop by default, schemathrowOnFailureopts into throwing).@rapiq/codec-url-expressionbuilt now as a sibling package.codecquery param + registry dispatch, defaulting to simple when absent.Changes
@rapiq/parser-simpleparameter/filters/value.ts):parseFilterScalar,parseFilterValue,parseFilterWireValue,serializeFilterValue— the single source for scalar coercion and operator-marker parsing. The three divergednormalizeValuecopies (simple parser, expression parser, codec encoder) are deleted.@rapiq/parser-expressioneq(name, 'a,b')parses to the plain string'a,b'(lists are separate args in this dialect). Previously this threw an untypedSyntaxError.@rapiq/codec-url-simpleencodenow throws typed errors instead of silently changing semantics —OPERATOR_UNSUPPORTEDfor REGEX/MOD/EXISTS/ELEM_MATCH (previously silently encoded as equality),FEATURE_UNSUPPORTEDfor same-field duplicates (previously last-write-wins), comma/empty values, and operator-marker collisions (e.g.eqon'foo~').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.decode(encode(q)) ≍ qacross the operator matrix, typed failures outside the subset.@rapiq/codec-url-expression(new)and/orcompounds cross the URL boundary in a singlefilter=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 reservedcodecparameter, decoding dispatches on it. Unstamped payloads fall back to the registry default (plain clients keep working); an unregistered stamped codec throws the new typedCodecError(ErrorCode.CODEC_UNRESOLVABLE, core) rather than silently mis-decoding. External codecs implement theURLCodecshape;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.mdrewritten: round-trip guarantee, per-dialect typed-failure matrices, schema-aware encoding, expression dialect, codec registry.guide/migration.mdlogging v1 behavior changes as introduced (~prefix position, expression quoted values, loud codec failures, strict mode, left-join default)..agents/{structure,architecture}.mdupdated; plan 007 and the roadmap M3 codec rows marked done.Breaking changes
encodethrows typed errors for queries outside the dialect subset (previously silent flattening/dropping/overwriting).~textchanges 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
Bug Fixes
Documentation