- Intro
- Class Api
- Functional Api
- Advanced
- Access Control
- Schema Versioning
- Permissions
- OperationStack
- Metrics
- Queries & Filters
- Catalogs & Collections
- Composing Workflows
- Developers
- Legacy
Object Schema Versioning in Hub.js
Managing the evolution of object structures (schemas) is a core challenge in long-lived applications, especially when you cannot rely on database-level migrations. In Hub.js, we address this by employing a run-time migration pattern for all objects whose structure may change over time.
The Problem
As the Hub.js library evolves, the shape of objects (such as entities, catalogs, or settings) may change. However, stored objects - whether in local storage, remote APIs, or other persistence layers - may still exist in older formats. We need a way to ensure that, whenever an object is loaded, it is automatically upgraded to the latest schema expected by the application.
The Pattern: Run-Time Migrations
The general pattern is as follows:
- Versioning: Any object whose schema may change over time includes a
.schemaVersion: number
property. This version indicates the schema the object currently conforms to. - Migration Functions: For each such object, we provide a migration function (e.g.,
upgradeCatalogSchema
) that takes an object of any version and returns an object in the latest schema. - Migration Chain: The migration function applies a chain of transformations, one for each version increment, so that even very old objects are brought up to date.
- Automatic Upgrades: When an entity is loaded (typically in its
fetch
process), its sub-objects are passed through the appropriate migration function. For example, anIHubCatalog
is upgraded viaupgradeCatalogSchema
inside the entity'scomputeProps
function.
Example: IHubCatalog
Suppose you have an IHubCatalog
object stored with schemaVersion: 1
. The current application expects schemaVersion: 3
. When a catalog is loaded as part of an Entity (e.g. a Hub Project), it is passed through upgradeCatalogSchema
, which applies migrations for v2 and v3 in sequence, resulting in an object that matches the latest schema.
function upgradeCatalogSchema(catalog: any): IHubCatalog {
let upgraded = { ...catalog };
if (!upgraded.schemaVersion || upgraded.schemaVersion < 2) {
upgraded = migrateToV2(upgraded);
upgraded.schemaVersion = 2;
}
if (upgraded.schemaVersion < 3) {
upgraded = migrateToV3(upgraded);
upgraded.schemaVersion = 3;
}
return upgraded as IHubCatalog;
}
Where Migrations Are Applied
- Entities: Most entities call their migration functions in their
computeProps
method, which is part of thefetch
process. - Sub-Graphs: Any sub-object (e.g., catalog, settings) that may have its own schema versioning is migrated independently.
- Special Cases: Some entities (like
IHubSite
) may have a different release schedule and handle migrations slightly differently, but the principle remains the same.
Benefits
- Reliability: The application can always expect to work with the latest schema, regardless of how old the stored object is.
- Flexibility: No need for database-level migrations; all upgrades happen at run-time.
- Backward Compatibility: Older objects are automatically upgraded, reducing the risk of runtime errors due to missing or outdated properties.
Summary
By versioning schemas and applying run-time migrations, Hub.js ensures that all objects are always in the expected format, enabling safe evolution of your data structures over time.