Problem
Entities live or die by round-tripping through storage, and today that story is entirely manual. The toJSON() / make() contract is sound, and examples/billing-persistence shows a persuasive repository for a document-shaped store — but a relational database raises questions the package and docs never answer:
- FK-vs-embedded nesting. A nested entity field (
issuedTo: Organization) serialises embedded in toJSON(). Relational persistence wants a foreign key on write and a join + make on read; that mapper is hand-written today, and branded field types make it cast-heavy. There is no documented pattern, let alone a helper.
- ORM guidance. No documented, tested integration story for Drizzle / Prisma / Kysely — even just type-level helpers or a recipe mapping
SomeEntity.output to a table shape would carry most of the value.
- Rows that predate the current model.
make validating against input (and re-deriving computed) already heals some drift, and the new evolve an entity how-to documents read-both-write-new mappers — but a first-class migrate-on-make hook (a versioned chain of row transforms applied before validation) would close the gap properly.
Why it matters
This is the highest-leverage gap in the library's usefulness: the sweet-spot adopter (zod-v4 hexagonal backend, DDD-flavoured) meets it on day one, and today the answer is "write it yourself".
Possible shape (to be designed)
- A documented pattern (docs first): reference-vs-embed decision guide, worked Drizzle/Kysely repository example with branded-type-safe mappers.
- Optionally, type-level helpers: e.g. a
Entity.Row<typeof X, { issuedTo: "id" }>-style mapped type replacing nested entity fields with their id type.
- Optionally,
make-side migration: SomeEntity.make(row, { migrate }) or a declaration option carrying versioned row transforms.
No runtime I/O enters the package in any variant — mapping and migration stay pure, per the no-I/O rule.
Problem
Entities live or die by round-tripping through storage, and today that story is entirely manual. The
toJSON()/make()contract is sound, andexamples/billing-persistenceshows a persuasive repository for a document-shaped store — but a relational database raises questions the package and docs never answer:issuedTo: Organization) serialises embedded intoJSON(). Relational persistence wants a foreign key on write and a join +makeon read; that mapper is hand-written today, and branded field types make it cast-heavy. There is no documented pattern, let alone a helper.SomeEntity.outputto a table shape would carry most of the value.makevalidating againstinput(and re-derivingcomputed) already heals some drift, and the new evolve an entity how-to documents read-both-write-new mappers — but a first-classmigrate-on-makehook (a versioned chain of row transforms applied before validation) would close the gap properly.Why it matters
This is the highest-leverage gap in the library's usefulness: the sweet-spot adopter (zod-v4 hexagonal backend, DDD-flavoured) meets it on day one, and today the answer is "write it yourself".
Possible shape (to be designed)
Entity.Row<typeof X, { issuedTo: "id" }>-style mapped type replacing nested entity fields with their id type.make-side migration:SomeEntity.make(row, { migrate })or a declaration option carrying versioned row transforms.No runtime I/O enters the package in any variant — mapping and migration stay pure, per the no-I/O rule.