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.settings.init();
await this.user.sync(); await this.user.sync();
await this.migrations.init();
await this.migrations.init();
await this.migrations.migrate(); await this.migrations.migrate();
} }

View File

@@ -1,5 +1,5 @@
import { CURRENT_DATABASE_VERSION } from "../common"; import { CURRENT_DATABASE_VERSION, EV } from "../common";
import { migrations } from "../migrations"; import Migrator from "../database/migrator";
class Migrations { class Migrations {
/** /**
@@ -8,58 +8,65 @@ class Migrations {
*/ */
constructor(db) { constructor(db) {
this._db = db; this._db = db;
this.dbVersion = CURRENT_DATABASE_VERSION; this._migrator = new Migrator();
} }
async init() { async init() {
this.dbVersion = (await this._db.context.read("v")) || 2; 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() { 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 = [ const collections = [
"notes", {
"notebooks", id: "notes",
"tags", index: this._db.notes.raw,
"colors", dbCollection: this._db.notes,
"trash", },
"delta", {
"text", id: "notebooks",
"content", index: this._db.notebooks.raw,
"settings", 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 this._migrator.migrate(collections, (item) => item, this.dbVersion);
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._db.context.write("v", CURRENT_DATABASE_VERSION); 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; export default Migrations;

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
import Hashes from "jshashes"; import Hashes from "jshashes";
import Migrator from "./migrator.js";
import { import {
CHECK_IDS, CHECK_IDS,
sendCheckUserStatusEvent, sendCheckUserStatusEvent,
CURRENT_DATABASE_VERSION, CURRENT_DATABASE_VERSION,
} from "../common.js"; } from "../common.js";
import { migrations } from "../migrations.js";
const md5 = new Hashes.MD5(); const md5 = new Hashes.MD5();
const invalidKeys = ["user", "t", "lastBackupTime"]; const invalidKeys = ["user", "t", "lastBackupTime"];
@@ -16,6 +16,7 @@ export default class Backup {
*/ */
constructor(db) { constructor(db) {
this._db = db; this._db = db;
this._migrator = new Migrator();
} }
lastBackupTime() { lastBackupTime() {
@@ -117,45 +118,56 @@ export default class Backup {
async _migrateData(backup) { async _migrateData(backup) {
const { data, version = 0 } = backup; const { data, version = 0 } = backup;
if (version > CURRENT_DATABASE_VERSION) if (version > CURRENT_DATABASE_VERSION)
throw new Error( throw new Error(
"This backup was made from a newer version of Notesnook. Cannot migrate." "This backup was made from a newer version of Notesnook. Cannot migrate."
); );
const collections = [ const collections = [
"notes", {
"notebooks", id: "notes",
"tags", index: data["notes"],
"colors", dbCollection: this._db.notes,
"trash", },
"delta", {
"text", id: "notebooks",
"content", index: data["notebooks"],
"settings", 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( await this._migrator.migrate(collections, (id) => data[id], version);
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);
})
);
})
);
} }
_validate(backup) { _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 = { export const migrations = {
handleDeleted: async function (db, collection, item) {
if (item.deleted) {
await db[collection]._collection.addItem(item);
return true;
}
return false;
},
0: { 0: {
notes: async function (db, item) { note: function (item) {
if (await migrations.handleDeleted(db, "notes", item)) return;
const contentId = item.content.delta; const contentId = item.content.delta;
const notebook = item.notebook; const notebook = item.notebook;
delete item.content; delete item.content;
@@ -17,69 +8,43 @@ export const migrations = {
item.contentId = contentId; item.contentId = contentId;
item.remote = true; item.remote = true;
if (notebook) item.notebooks = [notebook]; if (notebook) item.notebooks = [notebook];
await db.notes.add(item); return item;
}, },
delta: async function (db, item) { delta: function (item) {
if (await migrations.handleDeleted(db, "content", item)) return;
item.data = item.data.ops; item.data = item.data.ops;
item.type = "delta"; item.type = "delta";
await db.content.add(item); return item;
}, },
trash: async function (db, item) { trash: function (item) {
if (await migrations.handleDeleted(db, "trash", item)) return;
item.itemType = item.type; item.itemType = item.type;
item.type = "trash"; item.type = "trash";
if (item.itemType === "note") { if (item.itemType === "note") {
item.contentId = item.content.delta; item.contentId = item.content.delta;
delete item.content; delete item.content;
} }
await db.trash.add(item); return item;
}, },
text: function () {}, text: function () {},
}, },
2: { 2: {
notes: async function (db, item) { note: function (item) {
if (await migrations.handleDeleted(db, "notes", item)) return;
// notebook -> notebooks // notebook -> notebooks
const notebook = item.notebook; const notebook = item.notebook;
delete item.notebook; delete item.notebook;
item.remote = true; item.remote = true;
if (notebook) item.notebooks = [notebook]; if (notebook) item.notebooks = [notebook];
return item;
await db.notes.add({ ...item, remote: true });
}, },
}, },
3: { 3: {
notes: async function (db, item) { note: false,
if (await migrations.handleDeleted(db, "notes", item)) return; notebooks: function (item) {
await db.notes.add({ ...item, remote: true });
},
notebooks: async function (db, item) {
if (await migrations.handleDeleted(db, "notebooks", item)) return;
if (item.favorite !== undefined) delete item.favorite; if (item.favorite !== undefined) delete item.favorite;
await db.notebooks.add(item); return 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);
}, },
tag: false,
trash: false,
content: false,
settings: false,
}, },
}; };