Rename a prop in every stored screen
A button’s `label` becomes `text`. One migration, and a screen stored before the change is read as if it had been written after it.
pnpm add @niscorp/strata @niscorp/nova @niscorp/prism zodA screen that was stored
A screen in Nisc is a document: a tree of nodes, each naming a component. This one was stored last season, when a button’s words were its label. It has two buttons, one of them inside a card.
import { NOVA_SEQUENCE } from '@niscorp/nova/migrations';
import { evaluate } from '@niscorp/prism';
import { PRISM_SEQUENCE } from '@niscorp/prism/migrations';
import { createUpgrader, defineSequence } from '@niscorp/strata';
const stored = {
component: 'Stack',
children: [
{
component: 'Card',
children: [
{ component: 'Text', children: 'The Tempest, 19:30' },
{ component: 'Button', props: { label: 'Book seats' } },
],
},
{ component: 'Button', props: { label: 'All shows' } },
],
};The change, for one node
The migration is a Prism config. It is handed one node as $.document and answers with the node to put in its place.
It will be shown every node of the screen, so it starts by asking whether this one is a button with a label. If so it renames the key inside props. Any other node is handed back as it is.
const node = { $ref: '$.document' };
const isOldButton = {
$and: [
{ $eq: [{ $get: { from: node, path: ['component'], fallback: null } }, 'Button'] },
{ $has: { from: node, path: ['props', 'label'] } },
],
};
const renamed = {
$update: {
from: node,
path: ['props'],
value: { $renameKeys: { from: { $var: 'current' }, map: { label: 'text' } } },
},
};
const labelToText = { $case: { branches: [{ when: isOldButton, then: renamed }], else: node } };The kit’s sequence
The change belongs to the theatre’s kit, so the kit gets a sequence. documents is what says it is about documents and not tables. It is empty because the kit has no kind of its own: it rewrites Nova’s layouts, which at names.
const kit = defineSequence({
id: 'theatre.kit',
documents: {},
migrations: [
{
description: 'Button: label is now text',
steps: [{ kind: 'document', at: 'nisc.nova/layout', transform: labelToText }],
},
],
});An upgrader
An upgrader is made from every grammar a document can be written in. Nova’s is there because it is the one that says what a layout is and where layouts nest inside each other. Prism’s is there because Nova’s refers to it.
The transform is handed in: Strata does not depend on Prism, and calls whatever it is given.
const upgrader = await createUpgrader([NOVA_SEQUENCE, PRISM_SEQUENCE, kit], { transform: evaluate });Read the stored screen
The stamp is what was stored beside the screen: how far each grammar was when it was written. Here Nova’s and Prism’s are where they are today, and the kit’s is at 0, before the rename.
upgrade runs the migrations in between and answers with the screen as today’s code writes it, the refs of what it applied, and the stamp to store if you save it again.
const stamp = { ...upgrader.stamp, 'theatre.kit': 0 };
const read = upgrader.upgrade(stored, { kind: 'nisc.nova/layout', stamp });
console.log('applied:', read.applied);
console.log('stamp:', read.stamp);
console.dir(read.document, { depth: null });Run it.
The one migration that ran, the stamp the screen now carries, and the screen with text on both buttons, the one inside the card included.
npx tsx rename.mtsapplied: [ 'theatre.kit/1' ]
stamp: { 'nisc.nova': 2, 'nisc.prism': 3, 'theatre.kit': 1 }
{
component: 'Stack',
children: [
{
component: 'Card',
children: [
{ component: 'Text', children: 'The Tempest, 19:30' },
{ component: 'Button', props: { text: 'Book seats' } }
]
},
{ component: 'Button', props: { text: 'All shows' } }
]
}