Stamps, and refusing what is too new
What a stamp says, what happens at each distance from the code, and what Strata does with a document from a later release.
pnpm add @niscorp/strata @niscorp/prism zodA format with two changes behind it
A sequence that owns a format of document is called a grammar. It declares documents, the kinds it owns, and its migrations are document steps: at names the kind, and transform is a Prism config.
The config is handed one document as $.document and answers with the document to put in its place. The first moves one seat into a list. The second renames a key.
import { evaluate } from '@niscorp/prism';
import { StrataError, createUpgrader, defineSequence } from '@niscorp/strata';
const seatsFromSeat = {
$merge: [
{ $omit: { from: { $ref: '$.document' }, keys: ['seat'] } },
{ seats: [{ $ref: '$.document.seat' }] },
],
};
const memberFromHolder = { $renameKeys: { from: { $ref: '$.document' }, map: { holder: 'member' } } };
const tickets = defineSequence({
id: 'theatre.tickets',
documents: { ticket: {} },
migrations: [
{
description: 'seat is now seats, a list',
steps: [{ kind: 'document', at: 'theatre.tickets/ticket', transform: seatsFromSeat }],
},
{
description: 'holder is now member',
steps: [{ kind: 'document', at: 'theatre.tickets/ticket', transform: memberFromHolder }],
},
],
});
const upgrader = await createUpgrader([tickets], { transform: evaluate });The stamp to store
upgrader.stamp is what a document written now carries: each grammar, and how many migrations it has. Store it beside the document, in a column of its own.
console.log('written now:', upgrader.stamp);Behind the code
A ticket stored before either change has a stamp of 0, and both migrations run over it. One stored between the two has a stamp of 1, and only the second runs.
const kind = 'theatre.tickets/ticket';
const oldest = upgrader.upgrade({ holder: 'Grace', seat: 'C4' }, { kind, stamp: { 'theatre.tickets': 0 } });
console.log(oldest.applied, oldest.document);
const older = upgrader.upgrade({ holder: 'Ada', seats: ['A1'] }, { kind, stamp: { 'theatre.tickets': 1 } });
console.log(older.applied, older.document);No stamp at all
A document with no stamp is read as the start of every grammar. That is how rows from before you used Strata come in: everything runs over them.
const unstamped = upgrader.upgrade({ holder: 'Linus', seat: 'D2' }, { kind, stamp: null });
console.log(unstamped.applied.length, 'applied to a ticket with no stamp');Ahead of the code
A stamp of 3 was written by a release with a third migration, which this code does not have. Strata cannot know what that migration did, so it refuses with TOO_NEW.
This is what a server sees when it is rolled back, or when an add-on built on a newer version sends it a document. The fix is to run the newer code.
try {
upgrader.upgrade({ member: 'Mia', seats: ['B7'] }, { kind, stamp: { 'theatre.tickets': 3 } });
} catch (error) {
if (!(error instanceof StrataError)) throw error;
console.log(error.code, error.details);
}Is this one behind
behind answers whether a stamp has anything to run, without running it. It is how to count the rows that are still old.
console.log('behind at 1:', upgrader.behind({ 'theatre.tickets': 1 }));
console.log('behind at 2:', upgrader.behind({ 'theatre.tickets': 2 }));Run it.
The stamp a new ticket carries, what ran at each distance and what came out, the refusal with the two numbers it is about, and which stamps are behind.
npx tsx stamps.mtswritten now: { 'theatre.tickets': 2 }
[ 'theatre.tickets/1', 'theatre.tickets/2' ] { member: 'Grace', seats: [ 'C4' ] }
[ 'theatre.tickets/2' ] { member: 'Ada', seats: [ 'A1' ] }
2 applied to a ticket with no stamp
TOO_NEW [ 'theatre.tickets: the document is at 3, this code knows 2' ]
behind at 1: true
behind at 2: false