A Prisma schema, Zod validators and TypeScript types, generated together so they cannot drift apart.
—
—
—
—
// 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.
| config | prisma | zod | typescript |
|---|---|---|---|
| string | String | z.string() | string |
| number | Int | z.number() | number |
| float | Float | z.number() | number |
| decimal | Decimal | z.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. | |||
| bigint | BigInt | z.bigint() | bigint |
| boolean | Boolean | z.boolean() | boolean |
| date | DateTime | z.date() | Date |
| json | Json | z.unknown() | unknown |
| bytes | Bytes | z.instanceof(Uint8Array) | Uint8Array |
| uuid | String | z.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.
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.