Prism · guide

Shape a row

One row in, the object a reader needs out. A config is written in the shape of its result.

Install it
pnpm add @niscorp/prism @niscorp/strata zod
Step 1 of 3

A 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.

A booking
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,
};
Step 2 of 3

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.

A config, in the shape of the result
const forTheDoor = {
  name: { $ref: '$.member.name' },
  seats: { $join: { parts: { $ref: '$.seats' }, sep: ', ' } },
  admit: { $length: { $ref: '$.seats' } },
};
console.log(evaluate(forTheDoor, booking));
Step 3 of 3

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.

A field that may not be there
const forTheList = {
  name: { $ref: '$.member.name' },
  email: { $ref: '$.member.email' },
  __optional: ['email'],
};
console.log(evaluate(forTheList, booking));
Then

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.

Run it
npx tsx shape.mts
It prints
{ name: 'Grace', seats: 'C4, C5', admit: 2 }
{ name: 'Grace' }