@darlean/core
Version:
Darlean core functionality for creating applications that define, expose and host actors
172 lines (171 loc) • 7.3 kB
JavaScript
"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;