Shape a row
One row in, the object a reader needs out. A config is written in the shape of its result.
pnpm add @niscorp/prism @niscorp/strata zodA booking
A booking as the box office stores it: who, which show, which seats, and what a seat costs in cents. Every guide here reads this row or a few like it.
import { evaluate } from '@niscorp/prism';
const booking = {
id: 'B-2041',
member: { name: 'Grace', pass: 'season' },
show: { title: 'The Tempest', starts_at: '2026-10-24T19:30:00+02:00' },
seats: ['C4', 'C5'],
seat_price_cents: 3800,
};A config, in the shape of the result
A config is JSON. A plain object in it is the object that comes out, with each value worked out. An object whose one key starts with $ is an operator, and that is the only thing a $ key can be.
$ref reads a path in the source. $join makes one string of a list, and $length counts one.
const forTheDoor = {
name: { $ref: '$.member.name' },
seats: { $join: { parts: { $ref: '$.seats' }, sep: ', ' } },
admit: { $length: { $ref: '$.seats' } },
};
console.log(evaluate(forTheDoor, booking));A field that may not be there
A path that is not there is an error, not an empty string. Where a field may be missing, the config says so: __optional lists the fields to leave out when there is nothing to put in them.
const forTheList = {
name: { $ref: '$.member.name' },
email: { $ref: '$.member.email' },
__optional: ['email'],
};
console.log(evaluate(forTheList, booking));Run it.
What the usher at the door needs, and a row for a mailing list with no email in it, since Grace gave none.
npx tsx shape.mts{ name: 'Grace', seats: 'C4, C5', admit: 2 }
{ name: 'Grace' }