Rewrite a document
A config can change a document as well as read one. This is what a format change ships as.
pnpm add @niscorp/prism @niscorp/strata zodA 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.
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' } },
],
},
};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.
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)));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.
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)));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.
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);
}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.
npx tsx rewrite.mts{"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.