Each fix is pinned by a regression test in src/regressions.test.ts describing the
old behaviour.
- Inheritance dropped base-class rules. A subclass re-decorating an inherited
property registered its constraints against its own prototype, and validate()
read only the nearest set, so everything the base declared was silently lost.
Constraints are now merged down the whole prototype chain, base first, with
genuinely identical rules collapsed so restating @IsString() on an override
does not double-report. The library's own example.ts was affected: Media's
@IsString() title had never been enforced for Book.
- Circular references exhausted the heap. serialize() recursed forever, taking
8 GB and the process with it; it now tracks ancestors and raises a
JsonMappingError naming the cause. Diamonds still serialize. validate() skips
back-edges instead of recursing.
- @Matches with a g or y flag was stateful: RegExp.test advances lastIndex, so
validating the same value twice gave different answers. Those flags are
stripped.
- An unmatched @JsonPolymorphic discriminator silently dropped the value — the
single-object branch fell through without assigning. The raw value is now
preserved, with { onUnknown: 'error' } and { fallback } to choose otherwise.
- Custom @JsonSerialize serializers ran on null/undefined, crashing on any unset
optional property. They now only see real values.
- serialize() read obj.constructor.prototype, which throws for null-prototype
objects; both engines now agree on Object.getPrototypeOf.
- __proto__, constructor and prototype arriving in untrusted JSON were copied
onto the instance, detaching it from its own class. They are dropped.
- The "each element in ..." prefix was glued onto caller-supplied messages, and
two rules sharing a name overwrote each other so only one failure surfaced.
Also adds toInstanceArray/fromJsonArray, since toInstance and fromJson accept
arrays at runtime but type the result as T, and reports a non-JSON request body
in fromRequest as a JsonMappingError rather than a raw SyntaxError.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
The vitest suite silently ran zero tests: vitest 4 transpiles with oxc, which
does not pick up `experimentalDecorators` from a tsconfig that excludes the
files being transformed, so every decorator-using suite failed to parse and was
reported as "0 test". A vitest.config.ts enabling legacy decorators brings all
40 existing tests back to life.
- Add vitest.config.ts (oxc legacy decorators + v8 coverage config)
- Type-check test files: move the test/demo exclusions from the base tsconfig
onto the two build configs, and fix the strict-mode errors this surfaced
- Stop shipping src/example.ts in dist (it invokes runExample() at import time,
a side effect in a package declaring "sideEffects": false)
- CI: run lint, tests with coverage, build and entry-point smoke checks; drop
EOL Node 18, add Node 24; commit package-lock.json so `npm ci` works
- Add `verify`, `test:watch` and `build:docs` scripts, and an engines field;
`build:docs` regenerates the previously hand-maintained docs/cereale.js
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK