2021-02-04 19:15:23 +01:00
|
|
|
import EventEmitter from 'events';
|
2018-04-05 22:15:08 +02:00
|
|
|
|
2018-04-16 17:29:43 +02:00
|
|
|
/**
|
2020-11-10 18:30:41 +01:00
|
|
|
* @typedef {Object} Migration
|
2018-04-16 17:29:43 +02:00
|
|
|
* @property {number} version - The migration version
|
|
|
|
* @property {Function} migrate - Returns a promise of the migrated data
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2020-11-10 18:30:41 +01:00
|
|
|
* @typedef {Object} MigratorOptions
|
2018-04-16 17:29:43 +02:00
|
|
|
* @property {Array<Migration>} [migrations] - The list of migrations to apply
|
|
|
|
* @property {number} [defaultVersion] - The version to use in the initial state
|
|
|
|
*/
|
|
|
|
|
2020-04-26 04:32:41 +02:00
|
|
|
export default class Migrator extends EventEmitter {
|
2018-04-16 17:29:43 +02:00
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @param {MigratorOptions} opts
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
constructor(opts = {}) {
|
2021-02-04 19:15:23 +01:00
|
|
|
super();
|
|
|
|
const migrations = opts.migrations || [];
|
2017-05-12 03:15:59 +02:00
|
|
|
// sort migrations by version
|
2021-02-04 19:15:23 +01:00
|
|
|
this.migrations = migrations.sort((a, b) => a.version - b.version);
|
2017-05-12 03:15:59 +02:00
|
|
|
// grab migration with highest version
|
2021-02-04 19:15:23 +01:00
|
|
|
const lastMigration = this.migrations.slice(-1)[0];
|
2017-01-12 10:17:05 +01:00
|
|
|
// use specified defaultVersion or highest migration version
|
2020-11-03 00:41:28 +01:00
|
|
|
this.defaultVersion =
|
2021-02-04 19:15:23 +01:00
|
|
|
opts.defaultVersion || (lastMigration && lastMigration.version) || 0;
|
2017-01-12 10:17:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// run all pending migrations on meta in place
|
2020-11-03 00:41:28 +01:00
|
|
|
async migrateData(versionedData = this.generateInitialState()) {
|
2018-04-05 22:15:08 +02:00
|
|
|
// get all migrations that have not yet been run
|
2021-02-04 19:15:23 +01:00
|
|
|
const pendingMigrations = this.migrations.filter(migrationIsPending);
|
2017-05-12 03:15:59 +02:00
|
|
|
|
2018-04-05 22:15:08 +02:00
|
|
|
// perform each migration
|
2020-07-21 23:10:45 +02:00
|
|
|
for (const migration of pendingMigrations) {
|
2018-04-05 22:15:08 +02:00
|
|
|
try {
|
|
|
|
// attempt migration and validate
|
2021-02-04 19:15:23 +01:00
|
|
|
const migratedData = await migration.migrate(versionedData);
|
2019-11-20 01:03:20 +01:00
|
|
|
if (!migratedData.data) {
|
2021-02-04 19:15:23 +01:00
|
|
|
throw new Error('Migrator - migration returned empty data');
|
2019-11-20 01:03:20 +01:00
|
|
|
}
|
2020-11-03 00:41:28 +01:00
|
|
|
if (
|
|
|
|
migratedData.version !== undefined &&
|
|
|
|
migratedData.meta.version !== migration.version
|
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
'Migrator - Migration did not update version number correctly',
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2019-11-20 01:03:20 +01:00
|
|
|
}
|
2018-04-05 22:15:08 +02:00
|
|
|
// accept the migration as good
|
2020-08-15 13:58:11 +02:00
|
|
|
// eslint-disable-next-line no-param-reassign
|
2021-02-04 19:15:23 +01:00
|
|
|
versionedData = migratedData;
|
2018-04-05 22:15:08 +02:00
|
|
|
} catch (err) {
|
2018-04-05 22:38:34 +02:00
|
|
|
// rewrite error message to add context without clobbering stack
|
2021-02-04 19:15:23 +01:00
|
|
|
const originalErrorMessage = err.message;
|
|
|
|
err.message = `MetaMask Migration Error #${migration.version}: ${originalErrorMessage}`;
|
2018-04-05 22:15:08 +02:00
|
|
|
// emit error instead of throw so as to not break the run (gracefully fail)
|
2021-02-04 19:15:23 +01:00
|
|
|
this.emit('error', err);
|
2018-04-05 22:15:08 +02:00
|
|
|
// stop migrating and use state as is
|
2021-02-04 19:15:23 +01:00
|
|
|
return versionedData;
|
2018-04-05 22:15:08 +02:00
|
|
|
}
|
2017-05-12 03:15:59 +02:00
|
|
|
}
|
2017-05-11 10:46:17 +02:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
return versionedData;
|
2017-01-12 10:17:05 +01:00
|
|
|
|
2018-04-16 17:29:43 +02:00
|
|
|
/**
|
|
|
|
* Returns whether or not the migration is pending
|
|
|
|
*
|
|
|
|
* A migration is considered "pending" if it has a higher
|
|
|
|
* version number than the current version.
|
|
|
|
* @param {Migration} migration
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
function migrationIsPending(migration) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return migration.version > versionedData.meta.version;
|
2017-01-12 11:24:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-16 17:29:43 +02:00
|
|
|
/**
|
|
|
|
* Returns the initial state for the migrator
|
2020-01-13 19:36:36 +01:00
|
|
|
* @param {Object} [data] - The data for the initial state
|
2018-04-16 17:29:43 +02:00
|
|
|
* @returns {{meta: {version: number}, data: any}}
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
generateInitialState(data) {
|
2017-01-12 11:24:33 +01:00
|
|
|
return {
|
|
|
|
meta: {
|
|
|
|
version: this.defaultVersion,
|
|
|
|
},
|
2018-04-16 17:29:43 +02:00
|
|
|
data,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2017-01-12 10:17:05 +01:00
|
|
|
}
|
|
|
|
}
|