diff --git a/source/migrations/migrations.test.ts b/source/migrations/migrations.test.ts new file mode 100644 index 0000000..d3d5d3d --- /dev/null +++ b/source/migrations/migrations.test.ts @@ -0,0 +1,29 @@ +import {setup} from "@holllo/test"; + +import {dataMigrations, type QueueItemPre030} from "./migrations.js"; + +import snapshots from "./snapshots.json"; + +const queueItemSample: QueueItemPre030 = { + added: new Date("2022-03-02T16:00:00Z"), + id: 1, + text: "Sample", + url: "https://example.org", +}; + +await setup("Migrations", async (group) => { + group.test("Snapshots", async (test) => { + let data: Record = { + latestVersion: "0.1.0", + queue: [queueItemSample], + }; + + for (const [index, migration] of dataMigrations.entries()) { + data = (await migration.migrate(data)) as Record; + test.equals( + JSON.stringify(data, null, 2), + JSON.stringify(snapshots[index], null, 2), + ); + } + }); +}); diff --git a/source/migrations/migrations.ts b/source/migrations/migrations.ts new file mode 100644 index 0000000..88abf47 --- /dev/null +++ b/source/migrations/migrations.ts @@ -0,0 +1,115 @@ +import browser from "webextension-polyfill"; +import {migrate, type Migration} from "@holllo/migration-helper"; +import {createValue} from "@holllo/webextension-storage"; + +import type {ItemKeyPrefix} from "../item/item.js"; + +/** The Queue Item type for versions `<0.3.0`. */ +export type QueueItemPre030 = { + added: Date; + id: number; + text: string; + url: string; +}; + +/** The Queue Item type for versions `>=0.3.0 <1.0.0`. */ +export type QueueItem030 = { + sortIndex: number; +} & QueueItemPre030; + +/** The Queue Item type for versions `>=1.0.0`. */ +export type QueueItem100 = { + dateAdded: Date; + id: number; + text: string | undefined; + url: string; +}; + +/** All migrations for Queue storage. */ +export const dataMigrations: Array> = [ + { + version: "0.1.7", + async migrate(data: Record) { + const migrated: Record = { + version: "0.1.7", + }; + + const items = (data.queue as QueueItemPre030[]) ?? []; + for (const item of items) { + const key = `qi${item.id}`; + migrated[key] = item; + } + + return migrated; + }, + }, + { + version: "0.3.0", + async migrate(data: Record) { + const migrated: Record = { + version: "0.3.0", + }; + + for (const [key, value] of Object.entries(data)) { + if (key.startsWith("qi")) { + const item: QueueItem030 = { + sortIndex: value.id, + ...value, + }; + migrated[key] = item; + } + } + + return migrated; + }, + }, + { + version: "1.0.0", + async migrate(data: Record) { + const migrated: Record = { + version: "1.0.0", + }; + + for (const [key, value] of Object.entries(data)) { + if (key.startsWith("qi")) { + const item: QueueItem100 = { + dateAdded: new Date(value.added), + id: value.id, + text: value.text === "" ? undefined : value.text, + url: value.url, + }; + const prefix: ItemKeyPrefix = "item-"; + migrated[`${prefix}${item.id}`] = item; + } + } + + return migrated; + }, + }, +]; + +/** Run the migrations and apply the result to storage. */ +export async function runMigrations(): Promise { + const manifest = browser.runtime.getManifest(); + + const version = await createValue({ + deserialize: (input) => input, + serialize: (input) => input, + key: "version", + storage: browser.storage.sync, + value: manifest.version, + }); + + if (manifest.version >= version.value) { + return; + } + + const migrated = await migrate( + await browser.storage.sync.get(), + version.value, + dataMigrations, + ); + + await browser.storage.sync.clear(); + await browser.storage.sync.set(migrated as Record); +} diff --git a/source/migrations/snapshots.json b/source/migrations/snapshots.json new file mode 100644 index 0000000..e30e242 --- /dev/null +++ b/source/migrations/snapshots.json @@ -0,0 +1,30 @@ +[ + { + "version": "0.1.7", + "qi1": { + "added": "2022-03-02T16:00:00.000Z", + "id": 1, + "text": "Sample", + "url": "https://example.org" + } + }, + { + "version": "0.3.0", + "qi1": { + "sortIndex": 1, + "added": "2022-03-02T16:00:00.000Z", + "id": 1, + "text": "Sample", + "url": "https://example.org" + } + }, + { + "version": "1.0.0", + "item-1": { + "dateAdded": "2022-03-02T16:00:00.000Z", + "id": 1, + "text": "Sample", + "url": "https://example.org" + } + } +]