Strata · guide

Upgrade your own source

A screen written in a TypeScript file is a document too. Strata says what it must become, and holds your edit to that.

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

A screen in your source

This is what pnpm strata does in an app, with the parts laid out. Strata does not read your code. In an app, a script of your own lists the screens for it as objects; here there is one, written in place.

It was written when a button’s words were its label.

A screen in your source
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';
import { planSourceUpgrade, verifySourceUpgrade } from '@niscorp/strata/upgrade';

const ticket = {
  component: 'Stack',
  children: [
    { component: 'Text', children: 'The Tempest' },
    { component: 'Button', props: { label: 'Show my ticket' } },
  ],
};
const source = [{ kind: 'nisc.nova/layout', id: 'ticket', file: 'src/app/ticket.layout.ts', document: ticket }];
Step 2 of 5

A release that renames the prop

You install a release of the kit whose sequence has one more migration than your source was written at.

A release that renames the prop
const node = { $ref: '$.document' };
const labelToText = {
  $case: {
    branches: [
      {
        when: { $has: { from: node, path: ['props', 'label'] } },
        then: {
          $update: {
            from: node,
            path: ['props'],
            value: { $renameKeys: { from: { $var: 'current' }, map: { label: 'text' } } },
          },
        },
      },
    ],
    else: node,
  },
};
const kit = defineSequence({
  id: 'theatre.kit',
  documents: {},
  migrations: [
    {
      description: 'Button: label is now text',
      steps: [{ kind: 'document', at: 'nisc.nova/layout', transform: labelToText }],
    },
  ],
});
const upgrader = await createUpgrader([NOVA_SEQUENCE, PRISM_SEQUENCE, kit], { transform: evaluate });
Step 3 of 5

The plan

The lock is your source’s stamp: the file strata.lock.json, committed with the code. It says the source was written with the kit at 0.

The plan runs the migrations over each document and keeps the result. For every document a migration changes it holds the exact JSON it must become and a short list of what differs.

The plan
const lock = { ...upgrader.stamp, 'theatre.kit': 0 };
const plan = await planSourceUpgrade({
  upgrader,
  stamp: lock,
  documents: source,
  describe: (ref) => kit.migrations[Number(ref.split('/')[1]) - 1]?.description ?? ref,
});
const planned = plan.documents[0];
console.log(planned?.file, planned?.changes);
Step 4 of 5

Verify, before the edit

Strata does not edit the file. A person or a coding agent does, with the plan beside them. verify then compares. Run before anything was edited, it fails and says which file.

Verify, before the edit
const unedited = verifySourceUpgrade({ plan, upgrader, documents: source });
console.log('before the edit:', unedited.ok);
Step 5 of 5

Verify, after it

Here the edit is made by taking the JSON the plan expects. verify passes when every planned document equals it and nothing else was added, removed or changed. Then the lock moves to the new stamp.

In an app this is three commands: pnpm strata status --check in CI, pnpm strata upgrade to write the plan and a report, and pnpm strata verify once the files are edited.

Verify, after it
const edited = [{ ...source[0]!, document: planned?.expected }];
console.log('after the edit:', verifySourceUpgrade({ plan, upgrader, documents: edited }).ok);
console.log('the lock moves to:', plan.to);
Then

Run it.

The file the plan names and what changes in it, verify failing before the edit and passing after, and the stamp the lock then holds.

Run it
npx tsx source.mts
It prints
src/app/ticket.layout.ts [ '- children[1].props.label', '+ children[1].props.text' ]
before the edit: false
after the edit: true
the lock moves to: { 'nisc.nova': 2, 'nisc.prism': 3, 'theatre.kit': 1 }