UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

105 lines (84 loc) 3.92 kB
// SPDX-License-Identifier: Apache-2.0 import {type SchemaDefinition} from './schema-definition.js'; import {type SchemaMigration} from './schema-migration.js'; import {SemanticVersion} from '../../../../business/utils/semantic-version.js'; import {type ClassConstructor} from '../../../../business/utils/class-constructor.type.js'; import {type ObjectMapper} from '../../../mapper/api/object-mapper.js'; import {SchemaValidationError} from './schema-validation-error.js'; export abstract class SchemaDefinitionBase<T> implements SchemaDefinition<T> { public abstract get classConstructor(): ClassConstructor<T>; public abstract get migrations(): SchemaMigration[]; public abstract get name(): string; public abstract get version(): SemanticVersion<number>; protected constructor(protected readonly mapper: ObjectMapper) {} public async transform(data: object, sourceVersion?: SemanticVersion<number>): Promise<T> { if (data === undefined || data === null) { return null; } const clone: any = structuredClone(data); let dataVersion: number = clone.schemaVersion; if (!dataVersion) { dataVersion = sourceVersion ? sourceVersion.major : 0; } const migrated: object = await this.applyMigrations(clone, new SemanticVersion(dataVersion)); return this.mapper.fromObject(this.classConstructor, migrated); } public async validateMigrations(): Promise<void> { if (this.migrations.length === 0) { return; } // eslint-disable-next-line unicorn/no-array-sort const versionJumps: number[] = this.migrations.map((value): number => value.version.major).sort(); for (let index: number = 1; index < versionJumps.length; index++) { if (versionJumps[index] === versionJumps[index - 1]) { throw new SchemaValidationError(`Duplicate migration version '${versionJumps[index]}'`); } } let currentVersion: SemanticVersion<number> = this.nextVersionJump(new SemanticVersion(0)); for (const versionJump of versionJumps) { const v: SemanticVersion<number> = new SemanticVersion(versionJump); if (!v.equals(currentVersion)) { throw new SchemaValidationError( `Invalid migration version sequence detected; expected version '${v.major}' but got '${currentVersion.major}'`, ); } currentVersion = this.nextVersionJump(currentVersion); } return; } protected nextVersionJump(currentVersion: SemanticVersion<number>): SemanticVersion<number> { const targetMigrations: SchemaMigration[] = this.findMigrations(currentVersion); if (!targetMigrations || targetMigrations.length === 0) { // No migration found for the current version - fail with an error throw new SchemaValidationError( `No migration found for version '${currentVersion.major}'; there is a gap in the migration sequence`, ); } return targetMigrations[0].version; } /* * DV < version = 1 > * M1 < range = [0, 6), version = 6 > * M1.1 < range = [0, 4), version = 5 > * M2 < range = [6, 7), version = 8 > */ protected async applyMigrations(data: object, dataVersion: SemanticVersion<number>): Promise<object> { let migrations: SchemaMigration[] = this.findMigrations(dataVersion); while (migrations.length > 0) { const migration: SchemaMigration = migrations[0]; data = await migration.migrate(data); dataVersion = migration.version; migrations = this.findMigrations(dataVersion); } return data; } protected findMigrations(dataVersion: SemanticVersion<number>): SchemaMigration[] { const eligibleMigrations: SchemaMigration[] = this.migrations.filter((value): boolean => value.range.contains(dataVersion), ); if (eligibleMigrations.length > 0) { eligibleMigrations.sort((l, r): number => l.version.compare(r.version)); } return eligibleMigrations; } }