refactor: use a single migration backend

This commit is contained in:
thecodrr
2020-12-06 10:52:00 +05:00
parent 038214cc70
commit 048c74816a
7 changed files with 145 additions and 127 deletions

View File

@@ -76,8 +76,8 @@ class Database {
await this.settings.init();
await this.user.sync();
await this.migrations.init();
await this.migrations.init();
await this.migrations.migrate();
}

View File

@@ -1,5 +1,5 @@
import { CURRENT_DATABASE_VERSION } from "../common";
import { migrations } from "../migrations";
import { CURRENT_DATABASE_VERSION, EV } from "../common";
import Migrator from "../database/migrator";
class Migrations {
/**
@@ -8,58 +8,65 @@ class Migrations {
*/
constructor(db) {
this._db = db;
this.dbVersion = CURRENT_DATABASE_VERSION;
this._migrator = new Migrator();
}
async init() {
this.dbVersion = (await this._db.context.read("v")) || 2;
}
get _shouldMigrate() {
return this.dbVersion < CURRENT_DATABASE_VERSION;
}
_migrationFunction(collectionId) {
let migrationFunction = migrations[this.dbVersion][collectionId];
if (!migrationFunction)
migrationFunction = migrations[CURRENT_DATABASE_VERSION][collectionId];
return migrationFunction;
}
async migrate() {
if (!this._shouldMigrate) return;
if (this.dbVersion > CURRENT_DATABASE_VERSION) return;
await this._db.notes.init();
const content = await this._db.content.all();
const collections = [
"notes",
"notebooks",
"tags",
"colors",
"trash",
"delta",
"text",
"content",
"settings",
{
id: "notes",
index: this._db.notes.raw,
dbCollection: this._db.notes,
},
{
id: "notebooks",
index: this._db.notebooks.raw,
dbCollection: this._db.notebooks,
},
{
id: "tags",
index: this._db.tags.raw,
dbCollection: this._db.tags,
},
{
id: "colors",
index: this._db.colors.raw,
dbCollection: this._db.colors,
},
{
id: "trash",
index: this._db.trash.raw,
dbCollection: this._db.trash,
},
{
id: "content",
index: content,
dbCollection: this._db.content,
},
{
id: "settings",
index: [this._db.settings.raw],
dbCollection: this._db.settings,
},
];
await Promise.all(
collections.map(async (collectionId) => {
const collection = this._db[collectionId];
if (!collection) return;
const items =
collectionId === "content" || collectionId === "delta"
? await collection.all()
: collectionId === "settings"
? [collection.raw]
: collection.raw;
await Promise.all(
items.map(async (item) => {
await this._migrationFunction(collectionId)(this._db, item);
})
);
})
);
await this._migrator.migrate(collections, (item) => item, this.dbVersion);
await this._db.context.write("v", CURRENT_DATABASE_VERSION);
EV.publish("db:onMigrationDone", {
prev: this.dbVersion,
current: CURRENT_DATABASE_VERSION,
});
this.dbVersion = CURRENT_DATABASE_VERSION;
}
}
export default Migrations;

View File

@@ -8,6 +8,7 @@ class Settings {
constructor(db) {
this._db = db;
this._settings = {
type: "settings",
id: id(),
pins: [],
dateEdited: 0,

View File

@@ -1,3 +1,4 @@
import { CURRENT_DATABASE_VERSION } from "../../common";
import Database from "../index";
var tfun = require("transfun/transfun.js").tfun;
if (!tfun) {
@@ -43,6 +44,7 @@ class Collector {
return {
id: item.id,
v: CURRENT_DATABASE_VERSION,
...(await this._serialize(item)),
};
})(array)

View File

@@ -1,10 +1,10 @@
import Hashes from "jshashes";
import Migrator from "./migrator.js";
import {
CHECK_IDS,
sendCheckUserStatusEvent,
CURRENT_DATABASE_VERSION,
} from "../common.js";
import { migrations } from "../migrations.js";
const md5 = new Hashes.MD5();
const invalidKeys = ["user", "t", "lastBackupTime"];
@@ -16,6 +16,7 @@ export default class Backup {
*/
constructor(db) {
this._db = db;
this._migrator = new Migrator();
}
lastBackupTime() {
@@ -117,45 +118,56 @@ export default class Backup {
async _migrateData(backup) {
const { data, version = 0 } = backup;
if (version > CURRENT_DATABASE_VERSION)
throw new Error(
"This backup was made from a newer version of Notesnook. Cannot migrate."
);
const collections = [
"notes",
"notebooks",
"tags",
"colors",
"trash",
"delta",
"text",
"content",
"settings",
{
id: "notes",
index: data["notes"],
dbCollection: this._db.notes,
},
{
id: "notebooks",
index: data["notebooks"],
dbCollection: this._db.notebooks,
},
{
id: "tags",
index: data["tags"],
dbCollection: this._db.tags,
},
{
id: "colors",
index: data["colors"],
dbCollection: this._db.colors,
},
{
id: "trash",
index: data["trash"],
dbCollection: this._db.trash,
},
{
id: "delta",
index: data["delta"],
dbCollection: this._db.content,
},
{
id: "content",
index: data["content"],
dbCollection: this._db.content,
},
{
id: "settings",
index: ["settings"],
dbCollection: this._db.settings,
},
];
await Promise.all(
collections.map(async (collectionId) => {
let collection = data[collectionId];
if (!collection) return;
if (!Array.isArray(collection)) {
collection = [collectionId];
}
await Promise.all(
collection.map(async (id) => {
const item = data[id];
if (!item) return;
let migrationFunction = migrations[version][collectionId];
if (!migrationFunction)
migrationFunction =
migrations[CURRENT_DATABASE_VERSION][collectionId];
await migrationFunction(this._db, item);
})
);
})
);
await this._migrator.migrate(collections, (id) => data[id], version);
}
_validate(backup) {

View File

@@ -0,0 +1,31 @@
import { CURRENT_DATABASE_VERSION } from "../common";
import { migrations } from "../migrations";
class Migrator {
async migrate(collections, get, version) {
await Promise.all(
collections.map(async (collection) => {
if (!collection.index || !collection.dbCollection) return;
await Promise.all(
collection.index.map(async (id) => {
let item = get(id);
if (!item) return;
if (item.deleted)
return await collection.dbCollection._collection.addItem(item);
const migrate = migrations[version][item.type || collection.id];
if (migrate) item = migrate(item);
if (!!collection.dbCollection.merge) {
await collection.dbCollection.merge(item);
} else {
await collection.dbCollection.add(item);
}
})
);
})
);
return true;
}
}
export default Migrator;

View File

@@ -1,15 +1,6 @@
export const migrations = {
handleDeleted: async function (db, collection, item) {
if (item.deleted) {
await db[collection]._collection.addItem(item);
return true;
}
return false;
},
0: {
notes: async function (db, item) {
if (await migrations.handleDeleted(db, "notes", item)) return;
note: function (item) {
const contentId = item.content.delta;
const notebook = item.notebook;
delete item.content;
@@ -17,69 +8,43 @@ export const migrations = {
item.contentId = contentId;
item.remote = true;
if (notebook) item.notebooks = [notebook];
await db.notes.add(item);
return item;
},
delta: async function (db, item) {
if (await migrations.handleDeleted(db, "content", item)) return;
delta: function (item) {
item.data = item.data.ops;
item.type = "delta";
await db.content.add(item);
return item;
},
trash: async function (db, item) {
if (await migrations.handleDeleted(db, "trash", item)) return;
trash: function (item) {
item.itemType = item.type;
item.type = "trash";
if (item.itemType === "note") {
item.contentId = item.content.delta;
delete item.content;
}
await db.trash.add(item);
return item;
},
text: function () {},
},
2: {
notes: async function (db, item) {
if (await migrations.handleDeleted(db, "notes", item)) return;
note: function (item) {
// notebook -> notebooks
const notebook = item.notebook;
delete item.notebook;
item.remote = true;
if (notebook) item.notebooks = [notebook];
await db.notes.add({ ...item, remote: true });
return item;
},
},
3: {
notes: async function (db, item) {
if (await migrations.handleDeleted(db, "notes", item)) return;
await db.notes.add({ ...item, remote: true });
},
notebooks: async function (db, item) {
if (await migrations.handleDeleted(db, "notebooks", item)) return;
note: false,
notebooks: function (item) {
if (item.favorite !== undefined) delete item.favorite;
await db.notebooks.add(item);
},
tags: async function (db, item) {
if (await migrations.handleDeleted(db, "tags", item)) return;
await db.tags.merge(item);
},
colors: async function (db, item) {
if (await migrations.handleDeleted(db, "colors", item)) return;
await db.colors.merge(item);
},
trash: async function (db, item) {
if (await migrations.handleDeleted(db, "trash", item)) return;
await db.trash.add(item);
},
content: async function (db, item) {
if (await migrations.handleDeleted(db, "content", item)) return;
await db.content.add(item);
},
settings: async function (db, item) {
db.settings.merge(item);
return item;
},
tag: false,
trash: false,
content: false,
settings: false,
},
};