TRIO · DATA MODELLING

One config.
Three
layers.

A Prisma schema, Zod validators and TypeScript types, generated together so they cannot drift apart.

EDIT THE CONFIG. WATCH ALL THREE. the package's own generator, running here
schema.prisma
schemas.ts
types.ts

Three files that agree with each other is easy. The claim worth checking is what happens when it does not know a word.
THE PART THAT MATTERS

An unknown type is an error.
Never a fallback.

// what it used to do
const t = TYPES[cfg.type] || TYPES.string;

// { "balance": { "type": "decimal" } }
//   prisma  String
//   zod     z.string()
//   ts      string

A money column shipped as text. Prisma agreed, Zod agreed, TypeScript agreed — and all three were wrong in the same direction, so nothing anywhere raised a hand. Three layers that agree with each other and are all wrong together is the exact failure this tool exists to prevent, which made the fallback the bug.

$ npx @mrkt_frwd/trio schema.json --out ./generated   TRIO  schema.json  ────────────────────────────────────  ×  Account.balance: unknown type "wat". Known types: bigint, boolean,     bytes, date, decimal, float, json, number, string, uuid   refusing to generate — 1 problem(s). Nothing was written.

Nothing is written unless the whole config validates, so a bad field cannot leave a half-generated schema on disk. check() reports every problem at once rather than the first, because fixing one error at a time is how a five-minute job becomes an afternoon.

THE VOCABULARY

Ten types. Everything else stops.

configprismazodtypescript
stringStringz.string()string
numberIntz.number()number
floatFloatz.number()number
decimalDecimalz.string()string
Decimal is exact in the database and a string at the boundary — a JavaScript number cannot hold it losslessly. The generated schema carries that sentence as a comment, so the next person does not read it as a mistake and "fix" it.
bigintBigIntz.bigint()bigint
booleanBooleanz.boolean()boolean
dateDateTimez.date()Date
jsonJsonz.unknown()unknown
bytesBytesz.instanceof(Uint8Array)Uint8Array
uuidStringz.string().uuid()string

Ten is not a limitation to apologise for. It is the list the tool can keep three layers honest about — and the reason an eleventh word gets an error with the field name in it instead of a quiet String.

THE VERB

check / generate / write

checkEvery problem in the config, as a list. Empty means valid.
generateThe three files, in memory. Throws rather than returning a partial set.
generateSchemasValidates, then writes. Nothing reaches disk unless all of it does.
const { generate, generateSchemas, check } = require('@mrkt_frwd/trio');

check(config);                     // string[] of problems, empty if valid
generate(config);                  // { 'schema.prisma': …, 'schemas.ts': …, 'types.ts': … }
generateSchemas(config, './out');  // validates, then writes

Optionality is the field people get wrong by hand, so it is the one the three layers are generated from a single flag: optional becomes String?, .optional() and ?: together or not at all.

DOCS

Read it before you run it.

Packagenpm · MIT Agent readinessagents.md All seven enginesthe register