1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 01:03:35 +00:00

PM-3585 Improve state migrations (#5009)

* WIP: safer state migrations

Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>

* Add min version check and remove old migrations

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Add rollback and version checking

* Add state version move migration

* Expand tests and improve typing for Migrations

* Remove StateMigration Service

* Rewrite version 5 and 6 migrations

* Add all but initial migration to supported migrations

* Handle stateVersion location in migrator update versions

* Move to unique migrations directory

* Disallow imports outside of state-migrations

* Lint and test fixes

* Do not run migrations if we cannot determine state

* Fix desktop background StateService build

* Document Migration builder class

* Add debug logging to migrations

* Comment on migrator overrides

* Use specific property names

* `npm run prettier` 🤖

* Insert new migration

* Set stateVersion when creating new globals object

* PR comments

* Fix migrate imports

* Move migration building into `migrate` function

* Export current version from migration definitions

* Move file version concerns to migrator

* Update migrate spec to reflect new version requirements

* Fix import paths

* Prefer unique state data

* Remove unnecessary async

* Prefer to not use `any`

---------

Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
Matt Gibson
2023-08-30 12:57:20 -05:00
committed by GitHub
parent b444eed0b5
commit 3340af8084
51 changed files with 1538 additions and 980 deletions

View File

@@ -0,0 +1,106 @@
import { MigrationHelper } from "./migration-helper";
import { Direction, Migrator, VersionFrom, VersionTo } from "./migrator";
export class MigrationBuilder<TCurrent extends number = 0> {
/** Create a new MigrationBuilder with an empty buffer of migrations to perform.
*
* Add migrations to the buffer with {@link with} and {@link rollback}.
* @returns A new MigrationBuilder.
*/
static create(): MigrationBuilder<0> {
return new MigrationBuilder([]);
}
private constructor(
private migrations: readonly { migrator: Migrator<number, number>; direction: Direction }[]
) {}
/** Add a migrator to the MigrationBuilder. Types are updated such that the chained MigrationBuilder must currently be
* at state version equal to the from version of the migrator. Return as MigrationBuilder<TTo> where TTo is the to
* version of the migrator, so that the next migrator can be chained.
*
* @param migrate A migrator class or a tuple of a migrator class, the from version, and the to version. A tuple is
* required to instantiate version numbers unless a default constructor is defined.
* @returns A new MigrationBuilder with the to version of the migrator as the current version.
*/
with<
TMigrator extends Migrator<number, number>,
TFrom extends VersionFrom<TMigrator> & TCurrent,
TTo extends VersionTo<TMigrator>
>(
...migrate: [new () => TMigrator] | [new (from: TFrom, to: TTo) => TMigrator, TFrom, TTo]
): MigrationBuilder<TTo> {
return this.addMigrator(migrate, "up");
}
/** Add a migrator to rollback on the MigrationBuilder's list of migrations. As with {@link with}, types of
* MigrationBuilder and Migrator must align. However, this time the migration is reversed so TCurrent of the
* MigrationBuilder must be equal to the to version of the migrator. Return as MigrationBuilder<TFrom> where TFrom
* is the from version of the migrator, so that the next migrator can be chained.
*
* @param migrate A migrator class or a tuple of a migrator class, the from version, and the to version. A tuple is
* required to instantiate version numbers unless a default constructor is defined.
* @returns A new MigrationBuilder with the from version of the migrator as the current version.
*/
rollback<
TMigrator extends Migrator<number, number>,
TFrom extends VersionFrom<TMigrator>,
TTo extends VersionTo<TMigrator> & TCurrent
>(
...migrate: [new () => TMigrator] | [new (from: TFrom, to: TTo) => TMigrator, TTo, TFrom]
): MigrationBuilder<TFrom> {
if (migrate.length === 3) {
migrate = [migrate[0], migrate[2], migrate[1]];
}
return this.addMigrator(migrate, "down");
}
/** Execute the migrations as defined in the MigrationBuilder's migrator buffer */
migrate(helper: MigrationHelper): Promise<void> {
return this.migrations.reduce(
(promise, migrator) =>
promise.then(async () => {
await this.runMigrator(migrator.migrator, helper, migrator.direction);
}),
Promise.resolve()
);
}
private addMigrator<
TMigrator extends Migrator<number, number>,
TFrom extends VersionFrom<TMigrator> & TCurrent,
TTo extends VersionTo<TMigrator>
>(
migrate: [new () => TMigrator] | [new (from: TFrom, to: TTo) => TMigrator, TFrom, TTo],
direction: Direction = "up"
) {
const newMigration =
migrate.length === 1
? { migrator: new migrate[0](), direction }
: { migrator: new migrate[0](migrate[1], migrate[2]), direction };
return new MigrationBuilder<TTo>([...this.migrations, newMigration]);
}
private async runMigrator(
migrator: Migrator<number, number>,
helper: MigrationHelper,
direction: Direction
): Promise<void> {
const shouldMigrate = await migrator.shouldMigrate(helper, direction);
helper.info(
`Migrator ${migrator.constructor.name} (to version ${migrator.toVersion}) should migrate: ${shouldMigrate} - ${direction}`
);
if (shouldMigrate) {
const method = direction === "up" ? migrator.migrate : migrator.rollback;
await method(helper);
helper.info(
`Migrator ${migrator.constructor.name} (to version ${migrator.toVersion}) migrated - ${direction}`
);
await migrator.updateVersion(helper, direction);
helper.info(
`Migrator ${migrator.constructor.name} (to version ${migrator.toVersion}) updated version - ${direction}`
);
}
}
}