Strata · guide

Documents inside documents

A season holds shows and a show holds roles. One migration changes a role, and Strata finds every role wherever it is.

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

A season

One document, three levels deep. A season has a list of shows. A show has a cast, keyed by the part, and each entry is a role.

A season
import { evaluate } from '@niscorp/prism';
import { createUpgrader, defineSequence } from '@niscorp/strata';

const season = {
  name: 'Autumn 2026',
  shows: [
    {
      title: 'The Tempest',
      cast: { prospero: { actor: 'Alan' }, miranda: { actor: 'Ada' } },
    },
    {
      title: 'Twelfth Night',
      cast: { viola: { actor: 'Grace' } },
    },
  ],
};
Step 2 of 5

Say where one kind sits inside another

A grammar names its kinds, and for each kind where other documents sit inside it. That is its embeds: a path, and the kind found there.

A path is written with three things. A plain key is one document. [] after a key is every item of a list. * is every value of a record, whatever its key.

Say where one kind sits inside another
const documents = {
  season: { embeds: { 'shows[]': 'theatre.season/show' } },
  show: { embeds: { 'cast.*': 'theatre.season/role' } },
  role: {},
};
Step 3 of 5

A migration on the innermost kind

A role’s actor becomes player. The config is written for one role and knows nothing about shows or seasons.

A migration on the innermost kind
const theatre = defineSequence({
  id: 'theatre.season',
  documents,
  migrations: [
    {
      description: 'actor is now player',
      steps: [
        {
          kind: 'document',
          at: 'theatre.season/role',
          transform: { $renameKeys: { from: { $ref: '$.document' }, map: { actor: 'player' } } },
        },
      ],
    },
  ],
});
const upgrader = await createUpgrader([theatre], { transform: evaluate });
Step 4 of 5

Find every role

locate is the search Strata does before it runs a step. Told what kind the whole document is, it answers with every document inside it, each with its kind and the path to it. It is useful on its own, to see what a migration will touch.

Find every role
for (const found of upgrader.locate(season, 'theatre.season/season')) {
  console.log(found.kind, found.path.join('.'));
}
Step 5 of 5

Upgrade the season

The season is handed over as a whole, with its kind. Strata runs the migration on each role it found, deepest first, and puts each answer back where the role stood.

Upgrade the season
const read = upgrader.upgrade(season, { kind: 'theatre.season/season', stamp: { 'theatre.season': 0 } });
console.log(JSON.stringify(read.document.shows));
Then

Run it.

Every document in the season with its kind and its path, the season itself first, and then the shows with player in every cast.

Run it
npx tsx nested.mts
It prints
theatre.season/season 
theatre.season/show shows.0
theatre.season/role shows.0.cast.prospero
theatre.season/role shows.0.cast.miranda
theatre.season/show shows.1
theatre.season/role shows.1.cast.viola
[{"title":"The Tempest","cast":{"prospero":{"player":"Alan"},"miranda":{"player":"Ada"}}},{"title":"Twelfth Night","cast":{"viola":{"player":"Grace"}}}]