Prism · guide

Rewrite a document

A config can change a document as well as read one. This is what a format change ships as.

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

A stored screen

A screen an app stored a while ago. Since then a prop was renamed and a binding moved, and the document has to follow.

A stored screen
import { evaluate } from '@niscorp/prism';
import type { JsonValue } from '@niscorp/prism';

const stored: JsonValue = {
  document: {
    component: 'Stack',
    children: [
      { component: 'Text', children: '{{$.q}}' },
      { component: 'Stepper', props: { count: 3, caption: 'Seats' } },
    ],
  },
};
Step 2 of 4

Every node of the tree

$walk visits every node of a tree and rewrites the ones a rule matches. Here a binding is renamed wherever it stands, however deep.

Every node of the tree
const renamed = {
  $walk: {
    over: { $ref: '$.document' },
    as: 'node',
    rules: [{ when: { $eq: [{ $var: 'node' }, { $const: '{{$.q}}' }] }, then: { $const: '{{$.search}}' } }],
  },
};
console.log(JSON.stringify(evaluate(renamed, stored)));
Step 3 of 4

One path, and one set of keys

$update changes one path and leaves everything else identical; current is what stood there. $renameKeys renames keys in place.

One path, and one set of keys
const stepper = { $get: { from: { $ref: '$.document' }, path: ['children', 1] } };
const oneMore = { $add: [{ $var: 'current' }, { $const: 1 }] };
const bumped = { $update: { from: stepper, path: ['props', 'count'], value: oneMore } };
console.log(JSON.stringify(evaluate(bumped, stored)));
const props = { $get: { from: stepper, path: ['props'] } };
const relabelled = { $renameKeys: { from: props, map: { caption: 'label' } } };
console.log(JSON.stringify(evaluate(relabelled, stored)));
Step 4 of 4

Refuse loudly

A migration that cannot be right should not run. $assert answers its value when its condition holds, and fails with its own message when it does not.

Refuse loudly
const guarded = {
  $assert: {
    when: { $has: { from: { $ref: '$.document' }, path: ['id'] } },
    message: 'A document without an id cannot be migrated.',
    value: { $ref: '$.document' },
  },
};
try {
  evaluate(guarded, stored);
} catch (error) {
  console.log((error as Error).message);
}
Then

Run it.

The tree with its binding renamed, the stepper with its count raised, its props with one key renamed, and the refusal in the config’s own words.

Run it
npx tsx rewrite.mts
It prints
{"component":"Stack","children":[{"component":"Text","children":"{{$.search}}"},{"component":"Stepper","props":{"count":3,"caption":"Seats"}}]}
{"component":"Stepper","props":{"count":4,"caption":"Seats"}}
{"count":3,"label":"Seats"}
A document without an id cannot be migrated.