Releases: btravstack/entity
Release list
@btravstack/entity@0.3.0
Minor Changes
-
5f1a395: Two correctness fixes, honest
toJSONtyping, and readable errors.- Fix:
deepEqualno longer remembers failed comparisons as equal. The
cycle guard recorded every pair it entered and never forgot one that
finishedfalse, so twoSet/Mapfields with plainly different contents
could compare equal once their elements shared a subtree. The guard is now a
stack of in-progress pairs, not a memo. - Fix:
deepFreezeno longer freezes caller-owned values under a union
branch. The schema walk lost context atunion,pipeand
intersectionboundaries, so az.custom(...)value nested inside one was
frozen in place — mutating an object the caller still owns. The walk now
carries context through all three. toJSON()returnsDeepReadonly<Output>. The projection is shallow:
the top-level object is fresh, but nested containers are the instance's own
frozen references, so the previous mutable type let
toJSON().tags.push(…)compile and throw at runtime.InvalidEntity.messageis populated —"<entity>: <path>: <message>; …"—
so a log line or a failed assertion names the entity and the failing fields
instead of printing a blankError. The structuredissuesare unchanged.- New
Entity.renderIssueandEntity.keysOf— the issue helpers an
adapter needs to turn anInvalidEntityinto a response body, the same ones
the message is built from. - A duplicate union discriminant value is a declaration-time defect.
Entity.unionpreviously let the last member win while zod threw lazily at
the first parse; it now fails at the declaration, naming both members. - The construction seal's property is named
__useMakeOrFactoryInstead, so
the compile error onnew SomeEntity(…)tells the reader what to do.
- Fix:
-
ce69f0a:
update()rejects a patch key it cannot apply, instead of dropping it silently.A patch may now carry only keys
updateInputaccepts. A key that is
immutable,computed, or not a field of the entity at all comes back as an
InvalidEntitywith that key inpath— every offending key reports, not
just the first.All three were silently discarded before while
updatereturnedOk: the
caller asked for a change, got a success, and the change never happened. The
patch type already excluded them, but TypeScript's excess-property check only
fires on object literals, so the common adapter shape — building a patch as a
Record<string, unknown>from a request body — evaded it entirely and the key
vanished into a passingResult.makeis deliberately unchanged: it still ignores extra keys, so a stored row
carrying computed columns round-trips. Rehydrating data and patching it are
different acts — one heals what is already written, the other states an intent.Breaking for code that relied on the drop, most likely
update(someWholeOutputObject). Patch only the fields you mean to change, or
narrow the object first —updateInput.parse(body)strips unknown keys and
gives you a patch that is accepted by construction.
@btravstack/entity@0.2.0
Minor Changes
-
b5758a5: Declaration emit no longer expands the whole static surface into every consumer's
.d.ts.EntityStatic— whatEntity(tag)(fields, options)returns — was not exported,
so TypeScript had no name to write for it and serialised the entire static
surface structurally into any downstream package compiling with
declaration: true: the construct signature, all fourZodObjects, both zod
slots, the four phantom carriers andmake/extend/factory, with the field
map repeated a dozen times over. A one-field entity emitted a 274,048-byte
declaration; it is now 240.That expansion was two build failures, not a verbosity problem:
- a realistically wide domain enum (30 members, ordinary DDD widths) pushed the
repeated field map past the compiler's serialisation ceiling —TS7056,
fixable only by abandoningz.enumfor a branded string and losing both
runtime membership validation and compile-time exhaustiveness (#31); - a branded object field (
z.object({…}).brand("X")) was expanded through
DeepReadonlyuntil zod's module-private$brandsymbol reached
computed-key position, where it cannot be named across a module boundary —
TS4020(#32). Branded objects now work, and stay deep-readonly; the
"model it as a nested entity instead" workaround is no longer needed.
Both surfaced only at the consuming package's build, long after
tsc --noEmit,
the tests and everything else had gone green.EntityStaticis now a top-level export, andEntity.Staticfor anyone
annotating by hand. Both regressions are pinned by the consumer fixture.EntityUnionandUnionMemberare exported for the same reason, one type
further along: an exportedconstholding anEntity.union(...)had no
top-level name either, so TypeScript expanded its members structurally and
reached$brandthrough any branded field —TS4023: Exported variable 'X' has or is using name '$brand' … but cannot be named. Reported as the second error
in #32, and reproduced by declaring a union over an entity with a branded
Moneyfield.The zod peer range widens from
^4.4.0to^4.3.0. Nothing in the
implementation needed 4.4; the range was simply the version current at the
initial release. The floor is measured — the full surface typechecks, emits
declarations and passes its runtime assertions on 4.3.0. Monorepos that pin one
zod across every package no longer have to move the whole catalog, or relax the
peer locally, to adopt this (#33). - a realistically wide domain enum (30 members, ordinary DDD widths) pushed the
Patch Changes
- 9503929: Point the package README at the new documentation site,
https://btravstack.github.io/entity/, instead of the Markdown files in the
repository. No code change.