Migration and Changes
This guide explains how migration and change management work in Vona within the Cabloy monorepo.
Why this system exists
Vona provides a migration mechanism designed for long-lived modular projects.
Several important characteristics define this migration system:
- module-aware migration
- multi-tenant initialization support
- test-data support
- production-safe execution
- full access to the Vona ecosystem inside migration code
This makes migration in Vona more than a one-off schema script. It is part of the framework’s modular lifecycle.
Migration in the backend contract loop
A useful backend-contract-loop lifecycle mental model is:
- entity and model define the current structural contract
- DTOs and controllers define the API contract on top of that structure
meta.versionmanages schema evolution and initialization data- tests verify that the migrated backend still satisfies the intended contract
That is why migration should be understood as part of the same backend thread, not as a separate maintenance topic.
Define data version
Each module declares its current data version in its own package.json.
Representative pattern:
{
"name": "vona-module-demo-student",
"vonaModule": {
"fileVersion": 1
}
}The key rule is:
- increment
fileVersionwhen a released module introduces a new schema change that must be applied in sequence
In the scaffolded CRUD workflow, this is not an isolated maintenance step. The generator-driven thread treats fileVersion as part of the same backend evolution path that also touches entity/model/controller/test resources.
meta.version
Vona uses a bean named meta.version to organize migration code for a module.
Create it with:
npm run vona :create:bean meta version -- --module=demo-studentRepresentative shell:
@Meta()
export class MetaVersion extends BeanBase {}Three change scenarios
Three migration scenarios are defined:
| Scenario | Purpose |
|---|---|
update | schema evolution such as tables and fields |
init | instance- or tenant-specific initialization data |
test | test-only data for the test environment |
This split is one of the most important Vona migration ideas because it separates:
- structural change
- initialization logic
- test data setup
A practical generated-thread interpretation is:
updatefollows schema and entity/model evolutioninitfollows instance-aware initialization needs introduced by the backend featuretestkeeps the generated or refined contract easy to verify under the test lifecycle
Update: schema migration
Representative pattern:
@Meta()
export class MetaVersion extends BeanBase implements IMetaVersionUpdate {
async update(options: IMetaVersionUpdateOptions) {
if (options.version === 1) {
await this.bean.model.createTable('demoStudent', table => {
table.basicFields();
table.string('name', 50);
table.string('description', 255);
});
}
}
}A typed style based on entity metadata is also supported, and it is usually the better long-term default when possible.
Init: tenant-aware initialization
Representative pattern:
@Meta()
export class MetaVersion extends BeanBase implements IMetaVersionInit {
async init(options: IMetaVersionInitOptions) {
if (options.version === 1) {
await this.scope.model.student.insert({
name: 'Tom',
description: 'This is a student',
});
}
}
}The important point is that initialization can run per instance or tenant, which keeps tenant data isolated.
Test: test-only data
Representative pattern:
@Meta()
export class MetaVersion extends BeanBase implements IMetaVersionTest {
async test() {
await this.scope.model.student.insert({
name: 'Jimmy',
description: 'Only used in unit test',
});
}
}This is valuable because test data becomes part of the structured migration lifecycle instead of being scattered across unrelated setup code.
Version changes across the generated backend thread
When the generated CRUD thread evolves, migration should evolve with it.
A practical sequence is:
- increment
fileVersion - add or adjust entity/model structure
- update
meta.versionlogic - rerun migration locally
- verify the contract through tests and controller actions
This keeps schema change, backend contract change, and verification tightly connected.
Local development workflow
One important distinction for local development is:
- when iterating locally, you often want to recreate the database and re-run migration logic without bumping
fileVersion
Representative commands:
npm run test
cd vona && npm run db:resetA practical decision rule is:
- use
db:resetwhen you mainly need to replay migration/database setup locally - use
testwhen you need to verify that migration, controller behavior, and the broader generated contract thread still work together
Implementation checks for migration-sensitive changes
When changing backend schema or module initialization behavior, do not only edit entities and models.
Also ask:
- does this change require a
fileVersionincrement? - does
meta.versionneed anupdate,init, ortestbranch? - should the local verification path include
testordb:reset? - does the change affect the CRUD-generated thread or frontend-facing API contract as well?
That prevents schema changes from being documented in code but never integrated into the module lifecycle.