Skip to content

feat!: Support Zod v3/v4 and add strict parsing mode as the default - #3

Merged
razor-x merged 1 commit into
mainfrom
claude/url-param-parser-migration-i9foa1
Aug 5, 2026
Merged

feat!: Support Zod v3/v4 and add strict parsing mode as the default#3
razor-x merged 1 commit into
mainfrom
claude/url-param-parser-migration-i9foa1

Conversation

@razor-x

@razor-x razor-x commented Aug 5, 2026

Copy link
Copy Markdown
Member

Summary

Prerequisite for migrating nextlove off its hand-rolled query param parser (companion PR: seamapi/nextlove#182). Two changes:

  1. Zod v4 support — nextlove supports zod@^3 || ^4, but this package read Zod v3 internals (_def.typeName), so every schema built with Zod v4 threw UnparseableSchemaError.
  2. A strict option (default true) — strict mode only parses the serializer's expected output, making the parser a perfect inverse of @seamapi/url-search-params-serializer. Generous parsing (the previous behavior) moves behind strict: false, isolating its extra input formats and their limitations.

Strict mode (strict: true, the default)

Perfectly aligned with the serializer — only its output format is parsed:

  • Arrays use only the repeated format foo=a&foo=b (plus foo= for the empty array).
  • Array values may contain commas and are never splitfoo=a,b&foo=c parses as ['a,b', 'c']. The serializer can emit such values, so strict mode round-trips them; the "no commas in array values" limitation now only applies to generous mode.
  • There is no bracket array format. Since the serializer never outputs foo[]=a for an array named foo, a param named foo[] is unrelated to foo: it parses as a param literally named foo[] (which the serializer can produce for a key of that name, so it round-trips). The same applies to record keys ending in [] — literal keys in strict mode.
  • Booleans parse only from the exact strings true / false.
  • Whitespace is never trimmed and never treated as empty; only a truly empty value is null / the empty array.

New bijection tests cover the previously non-invertible cases: array values containing commas, and params literally named with a bracket suffix.

Generous mode (strict: false)

Preserves the previous behavior: comma (foo=a,b) and bracket (foo[]=a) array formats, generous booleans (yes, 1, NO, …), and whitespace trimming/null handling — at the cost of array values not containing commas. Unchanged in both modes: unparseable values pass through as strings for the schema to reject, empty z.string() is null, unknown params are ignored.

Zod v3/v4 compatibility

  • Internals are read from _zod.def (v4) with fallback to _def (v3); v4 type strings are normalized to v3-style type names. peerDependencies widened to zod@^3.0.0 || ^4.0.0.
  • Removed the runtime import of ZodFirstPartyTypeKind, which Zod v4 no longer exports (it would fail at module load).
  • Effects and pipelines (.refine() / .transform() / z.preprocess() / .pipe()) are unwrapped to the schema describing the parser input; applying the effect is left to the schema. Previously these threw UnparseableSchemaError.
  • Enum value types are derived from values (v4 collapses nativeEnum into enum): all-string enums parse as strings, all-numeric as numbers. Zod v4 multi-value literals (z.literal(['a','b'])) are supported.
  • Dev-only: TypeScript ~5.3.3~5.8.3 (Zod v4 types need TS ≥ 5.4).

Testing

166 tests pass: the existing Zod v3 suite (updated to pass strict: false where it exercises generous parsing), a new test/strict-parsing.test.ts (including literal foo[] params), and a new test/zod-v4.test.ts running core behavior against Zod v4 via a zod-v4 npm alias.

Breaking changes

  • Generous parsing is no longer the default: pass strict: false to keep the previous behavior.
  • Refined/transformed schemas and numeric enums now parse instead of throwing.

Semantic-release: publishes as 0.1.0 (0.x minor bump for the breaking feat!).

Read schema internals from _zod.def (Zod v4) or _def (Zod v3) and
normalize Zod v4 type names to Zod v3 style names. Remove the runtime
dependency on ZodFirstPartyTypeKind, which Zod v4 no longer exports.

Unwrap effects (Zod v3 refine/transform/preprocess) and pipelines
(Zod v4 transform) to the schema describing the parser input, so
refined and transformed schemas are now parseable instead of throwing
UnparseableSchemaError. Applying the effect is left to the schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jWzL9LC7Q6bTGaXDoKq4z
@razor-x
razor-x marked this pull request as ready for review August 5, 2026 21:13
@razor-x
razor-x merged commit 4ac8bdf into main Aug 5, 2026
13 checks passed
@razor-x
razor-x deleted the claude/url-param-parser-migration-i9foa1 branch August 5, 2026 21:13
@razor-x razor-x changed the title feat: Support Zod v3 and Zod v4 schemas feat!: Support Zod v3/v4 and add strict parsing mode as the default Aug 5, 2026
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