Refused, safely
A config can come from a model or a tenant. What Prism does with one that is wrong, and how to ask without a throw.
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 { PrismError, evaluateSafe, validate } 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 refusal's code, as Prism names it
const codeOf = (error: Error): string => (error instanceof PrismError ? error.code : error.name);An operator that does not exist
There is no $fetch, and no operator like it: none opens a file, calls a network or runs a string. validate reads a config against the schema without running it, and names the key.
const reachesOut = { card: { $fetch: { url: 'https://example.com/cards' } } };
const checked = validate(reachesOut);
console.log(checked.ok, checked.ok ? '' : checked.issues[0]);A path that is not there
A valid config can still fail on the data it is given. evaluateSafe answers { ok, data } or { ok, error } and never throws, and the error carries a code.
const missing = evaluateSafe({ email: { $ref: '$.member.email' } }, booking);
console.log(missing.ok, missing.ok ? '' : codeOf(missing.error));A config that grows without end
Forty passes, each doubling a string. Prism counts what an evaluation makes, and stops at its limit, a million characters, long before the fortieth.
const passes = Array.from({ length: 40 }, (_unused, index) => index);
const endless = {
$reduce: {
over: { $const: passes },
as: 'pass',
acc: 'text',
init: { $const: 'seat ' },
body: { $join: { parts: [{ $var: 'text' }, { $var: 'text' }], sep: '' } },
},
};
const grown = evaluateSafe(endless, booking);
console.log(grown.ok, grown.ok ? '' : codeOf(grown.error));Run it.
Three refusals: one by the schema before anything ran, one for a path the row does not have, and one at the budget.
npx tsx refused.mtsfalse {
path: [ 'card', '$fetch' ],
message: 'Not a Prism op. A key that starts with "$" names an op; data with such a key goes in $const.'
}
false E_MISSING_PATH
false E_BUDGET