ObjectStackObjectStack

Migration

Migration protocol schemas

Migration protocol — the two kinds of migration, kept apart on purpose.

A schema migration (ChangeSet + its atomic operations) reshapes the physical database to match metadata: add a field, change a type, create an object, run SQL. It is derivable from the metadata and applied by os migrate plan / os migrate apply.

A data migration rewrites the rows themselves, and whether it is done is a property of ONE DEPLOYMENT's database rather than of the installed code version — so it cannot be expressed as a ChangeSet, and its completion cannot be inferred from a release. DataMigrationFlag is the per-deployment record that one ran here and its self-check passed; consumers that would act irreversibly on migrated data gate on the flag instead of the version.

Source: packages/spec/src/system/migration.zod.ts

TypeScript Usage

import { AddFieldOperation, ChangeSetSchema, CreateObjectOperation, DataMigrationFlagSchema, DeleteObjectOperation, ExecuteSqlOperation, MigrationDependencySchema, MigrationJournalEventSchema, MigrationOperationSchema, ModifyFieldOperation, RemoveFieldOperation, RenameObjectOperation } from '@objectstack/spec/system';
import type { ChangeSet, DataMigrationFlag, DeleteObjectOperation, ExecuteSqlOperation, MigrationDependency, MigrationJournalEvent, MigrationOperation, ModifyFieldOperation, RemoveFieldOperation, RenameObjectOperation } from '@objectstack/spec/system';

// Validate data
const result = AddFieldOperation.parse(data);

AddFieldOperation

Add a new field to an existing object

Properties

PropertyTypeRequiredDescription
type'add_field'
objectNamestringTarget object name
fieldNamestringName of the field to add
field{ name?: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; description?: string; … }Full field definition to add

ChangeSet

A versioned set of atomic schema migration operations

Properties

PropertyTypeRequiredDescription
idstringUnique identifier for this change set
namestringHuman readable name for the migration
descriptionstringoptionalDetailed description of what this migration does
authorstringoptionalAuthor who created this migration
createdAtstringoptionalISO 8601 timestamp when the migration was created
dependencies{ migrationId: string; package?: string }[]optionalMigrations that must run before this one
operations({ type: 'add_field'; objectName: string; fieldName: string; field: object } | { type: 'modify_field'; objectName: string; fieldName: string; changes: Record<string, any> } | { type: 'remove_field'; objectName: string; fieldName: string } | { type: 'create_object'; object: object } | … +3 more)[]Ordered list of atomic migration operations
rollback({ type: 'add_field'; objectName: string; fieldName: string; field: object } | { type: 'modify_field'; objectName: string; fieldName: string; changes: Record<string, any> } | { type: 'remove_field'; objectName: string; fieldName: string } | { type: 'create_object'; object: object } | … +3 more)[]optionalOperations to reverse this migration

CreateObjectOperation

Create a new object

Properties

PropertyTypeRequiredDescription
type'create_object'
object{ name: string; label?: string; pluralLabel?: string; description?: string; … }Full object definition to create

DataMigrationFlag

Deployment-level record that a data migration ran here and its self-check passed — the evidence gate consumers read instead of the platform version

Properties

PropertyTypeRequiredDescription
idstringMigration id (e.g. adr-0104-file-references) — one row per data migration
last_run_atstringWhen this migration last completed a gated (apply-mode) run on this deployment
verified_atstring | nulloptionalWhen the self-check last PASSED. Null/absent until it does — and cleared again by a later failing run, so a regression closes the gate
applied_atstring | nulloptionalWhen the backfill last ran in apply mode (writes enabled)
blockingintegerBlocking discrepancies reported by the last self-check. The gate requires 0
advisoryintegeroptionalAdvisory findings from the last run (external URLs, stale owners, …) — cost storage or need a modelling decision, never block the gate
detailsstringoptionalJSON-encoded counts from the last run, for diagnostics
deviation_observed_atstring | nulloptionalWhen this deployment last ADMITTED a value the verified contract rejects, via an OS_ALLOW_LAX_* escape hatch. Does not clear verified_at — it withdraws the irreversible half of what the certificate authorises (#4797)
deviation_detailstring | nulloptionalJSON-encoded first counterexample behind deviation_observed_at (object, field, type, parse issue), for diagnostics

DeleteObjectOperation

Delete an existing object

Properties

PropertyTypeRequiredDescription
type'delete_object'
objectNamestringName of the object to delete

ExecuteSqlOperation

Execute a raw SQL statement

Properties

PropertyTypeRequiredDescription
type'execute_sql'
sqlstringRaw SQL statement to execute
descriptionstringoptionalHuman-readable description of the SQL

MigrationDependency

Dependency reference to another migration that must run first

Properties

PropertyTypeRequiredDescription
migrationIdstringID of the migration this depends on
packagestringoptionalPackage that owns the dependency migration

MigrationJournalEvent

One event in a migration run journal — the durable trace that lets a killed run be resumed forward or compensated back, with rows proving which

Properties

PropertyTypeRequiredDescription
run_idstringIdentifies one run. Rows are keyed (run_id, seq)
seqintegerMonotonic per-run sequence. Ordering authority — wall-clock timestamps can tie or skew
kindEnum<'run_started' | 'chunk_started' | 'chunk_done' | 'compensated' | 'run_done' | 'run_failed'>Event kind
migration_idstringoptionalThe named migration this run belongs to, when it has one — joins to sys_migration.id
plan_hashstringoptionalOn run_started: hash of the plan shape. A resume whose plan hash differs REFUSES rather than resuming a changed plan against an old journal
chunk_indexintegeroptionalOn chunk_started / chunk_done / compensated: the run-global chunk index
attemptintegeroptionalWhich attempt produced this event. attempt > 1 means a prior outcome was unknown and the callback was asked to recheck by natural key
detailstringoptionalJSON-encoded payload — the chunk plan on run_started, the error on run_failed / a failed compensation
created_atstringoptionalWall-clock stamp, for humans. Never the ordering authority — that is seq

MigrationOperation

Union Options

This schema accepts one of the following structures:

Option 1

Add a new field to an existing object

Type: add_field

Properties

PropertyTypeRequiredDescription
type'add_field'
objectNamestringTarget object name
fieldNamestringName of the field to add
field{ name?: string; label?: string; type: Enum<'text' | 'textarea' | 'email' | 'url' | 'phone' | 'password' | 'secret' | … +42 more>; description?: string; … }Full field definition to add

Option 2

Modify properties of an existing field

Type: modify_field

Properties

PropertyTypeRequiredDescription
type'modify_field'
objectNamestringTarget object name
fieldNamestringName of the field to modify
changesRecord<string, any>Partial field definition updates

Option 3

Remove a field from an existing object

Type: remove_field

Properties

PropertyTypeRequiredDescription
type'remove_field'
objectNamestringTarget object name
fieldNamestringName of the field to remove

Option 4

Create a new object

Type: create_object

Properties

PropertyTypeRequiredDescription
type'create_object'
object{ name: string; label?: string; pluralLabel?: string; description?: string; … }Full object definition to create

Option 5

Rename an existing object

Type: rename_object

Properties

PropertyTypeRequiredDescription
type'rename_object'
oldNamestringCurrent object name
newNamestringNew object name

Option 6

Delete an existing object

Type: delete_object

Properties

PropertyTypeRequiredDescription
type'delete_object'
objectNamestringName of the object to delete

Option 7

Execute a raw SQL statement

Type: execute_sql

Properties

PropertyTypeRequiredDescription
type'execute_sql'
sqlstringRaw SQL statement to execute
descriptionstringoptionalHuman-readable description of the SQL


ModifyFieldOperation

Modify properties of an existing field

Properties

PropertyTypeRequiredDescription
type'modify_field'
objectNamestringTarget object name
fieldNamestringName of the field to modify
changesRecord<string, any>Partial field definition updates

RemoveFieldOperation

Remove a field from an existing object

Properties

PropertyTypeRequiredDescription
type'remove_field'
objectNamestringTarget object name
fieldNamestringName of the field to remove

RenameObjectOperation

Rename an existing object

Properties

PropertyTypeRequiredDescription
type'rename_object'
oldNamestringCurrent object name
newNamestringNew object name

On this page