UNPKG

@darlean/core

Version:

Darlean core functionality for creating applications that define, expose and host actors

172 lines (171 loc) 7.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MigrationController = void 0; const base_1 = require("@darlean/base"); const utils_1 = require("@darlean/utils"); class MigrationController { constructor(migrations) { this.migrations = migrations; } /** * Loads the provided persistable and checks whether the known list of migrations supports * the persistable's migration state. * @param persistable */ async checkCompatibility(persistable) { const stateValue = await persistable.load(); const isNewState = stateValue === undefined; if (isNewState) { return; } const info = this.extractMigrationInfo(stateValue); if (!info) { return; } const stateVersion = this.extractMigrationVersion(info); const stateVersionMajor = this.extractMajor(stateVersion); const supportedVersion = this.obtainLatestSupportedVersion(); const supportedVersionMajor = this.extractMajor(supportedVersion); if (supportedVersionMajor < stateVersionMajor) { throw new base_1.FrameworkError(base_1.FRAMEWORK_ERROR_MIGRATION_ERROR, 'Internal state reports version [StateVersion] which is incompatible with the current software which supports up to version [SupportedVersion]', { [base_1.FRAMEWORK_ERROR_PARAMETER_MIGRATION_VERSION]: (0, utils_1.encodeNumber)(stateVersionMajor), StateVersion: stateVersion, SupportedVersion: supportedVersion }); } return info; } getContext(c) { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; return { perform: async function (state, nameResolver, defaultValue) { return await self.perform(state, nameResolver, c, defaultValue); } }; } enforceMigrationInfo(info, persistable) { if (!info) { return; } if (!persistable.hasValue()) { const state = { migrationInfo: info }; persistable.change(state); } else { persistable.getValue().migrationInfo = info; persistable.markDirty(); } } enforceMigrationInfoOnState(state) { // Only set the state when it was not present yet. This avoids issues durihg mmigration when // the migration framework sets an explicit version less than the latest supported version. if (!state.migrationInfo) { const encoded = this.encodeMigrationVersion(this.obtainLatestSupportedVersion()); if (typeof state !== 'object') { return false; } state.migrationInfo = encoded; return true; } return false; } extractMigrationInfo(value) { return value ? value.migrationInfo : undefined; } extractMigrationVersion(info) { if (info) { return info.split(';')[0]; } } encodeMigrationVersion(version) { return version; } obtainLatestSupportedVersion() { return this.migrations?.[this.migrations.length - 1]?.version; } async perform(persistable, nameResolver, context, defaultValue) { const stateValue = await persistable.load(); const isNewState = stateValue === undefined; if (isNewState) { const supportedVersion = this.migrations?.[this.migrations.length - 1]?.version; const state = { ...defaultValue }; state.migrationInfo = supportedVersion; persistable.change(state); return context; } const info = this.extractMigrationInfo(stateValue); let stateVersion = this.extractMigrationVersion(info); const stateVersionMajor = this.extractMajor(stateVersion); const supportedVersion = this.migrations?.[this.migrations.length - 1]?.version; const supportedVersionMajor = this.extractMajor(supportedVersion); if (supportedVersionMajor < stateVersionMajor) { throw new base_1.FrameworkError(base_1.FRAMEWORK_ERROR_MIGRATION_ERROR, 'Internal state reports version [StateVersion] which is incompatible with the current software which supports up to version [SupportedVersion]', { [base_1.FRAMEWORK_ERROR_PARAMETER_MIGRATION_VERSION]: (0, utils_1.encodeNumber)(stateVersionMajor), StateVersion: stateVersion, SupportedVersion: supportedVersion }); } let name; let c = context; for (const migration of this.migrations) { if (this.secondVersionIsNewerOrEqual(migration.version, stateVersion ?? '')) { continue; } if (name === undefined) { name = await nameResolver(); } (0, utils_1.notifier)().info('MIGRATION_STARTED', 'Started migration [MigrationVersion] ([MigrationName]) for [Subject]', () => ({ MigrationVersion: migration.version, MigrationName: migration.name, Subject: name })); try { c = (await migration.migrator(persistable, c)) ?? c; this.enforceMigrationInfo(this.encodeMigrationVersion(migration.version), persistable); await persistable.persist('always'); stateVersion = migration.version; (0, utils_1.notifier)().info('MIGRATION_FINISHED', 'Finished migration [MigrationVersion] ([MigrationName]) for [Subject]', () => ({ MigrationVersion: migration.version, MigrationName: migration.name, Subject: name })); } catch (e) { (0, utils_1.notifier)().error('MIGRATION_FAILED', 'Failed migration [MigrationVersion] [MigrationName] for [Subject]: [Error]', () => ({ MigrationVersion: migration.version, MigrationName: migration.name, Subject: name, Error: e })); throw e; } } return c; } secondVersionIsNewerOrEqual(first, second) { const firstNormalized = this.normalizeVersion(first) .map((x) => x.toString().padStart(10, '0')) .join('.'); const secondNormalized = this.normalizeVersion(second) .map((x) => x.toString().padStart(10, '0')) .join('.'); return secondNormalized >= firstNormalized; } extractMajor(version) { if (!version) { return 0; } return parseInt(version.split('.')[0]); } normalizeVersion(v) { let parts = v.split('.').map((x) => (x === '' ? 0 : parseInt(x))); while (parts[parts.length] === 0) { parts = parts.slice(0, parts.length - 1); } return parts; } } exports.MigrationController = MigrationController;