2024-07-18 21:35:17 +03:00
import backupService from "./backup.js" ;
import sql from "./sql.js" ;
import log from "./log.js" ;
2025-01-02 13:47:44 +01:00
import { crash } from "./utils.js" ;
2024-07-18 21:35:17 +03:00
import appInfo from "./app_info.js" ;
import cls from "./cls.js" ;
2025-02-20 18:06:19 +02:00
import { t } from "i18next" ;
2025-05-30 21:32:35 +03:00
import MIGRATIONS from "../migrations/migrations.js" ;
2024-02-17 19:42:30 +02:00
interface MigrationInfo {
dbVersion : number ;
2025-03-02 20:43:01 +02:00
/ * *
2025-05-30 21:32:35 +03:00
* If string , then the migration is an SQL script that will be executed .
* If a function , then the migration is a JavaScript / TypeScript module that will be executed .
2025-03-02 20:43:01 +02:00
* /
2025-05-30 21:32:35 +03:00
migration : string | ( ( ) = > void ) ;
2024-02-17 19:42:30 +02:00
}
async function migrate() {
const currentDbVersion = getDbVersion ( ) ;
if ( currentDbVersion < 214 ) {
2025-02-20 18:06:19 +02:00
await crash ( t ( "migration.old_version" ) ) ;
2024-02-17 19:42:30 +02:00
return ;
}
// backup before attempting migration
2025-06-04 21:44:27 +03:00
if ( ! process . env . TRILIUM_INTEGRATION_TEST ) {
await backupService . backupNow (
// creating a special backup for version 0.60.4, the changes in 0.61 are major.
currentDbVersion === 214 ? ` before-migration-v060 ` : "before-migration"
) ;
}
2024-02-17 19:42:30 +02:00
2025-03-02 20:43:01 +02:00
const migrations = await prepareMigrations ( currentDbVersion ) ;
2024-02-17 19:42:30 +02:00
// all migrations are executed in one transaction - upgrade either succeeds, or the user can stay at the old version
// otherwise if half of the migrations succeed, user can't use any version - DB is too "new" for the old app,
// and too old for the new app version.
cls . setMigrationRunning ( true ) ;
2025-03-02 20:43:01 +02:00
sql . transactional ( ( ) = > {
2024-02-17 19:42:30 +02:00
for ( const mig of migrations ) {
try {
log . info ( ` Attempting migration to version ${ mig . dbVersion } ` ) ;
2025-03-02 20:43:01 +02:00
executeMigration ( mig ) ;
2024-02-17 19:42:30 +02:00
2025-01-09 18:07:02 +02:00
sql . execute (
2025-04-01 23:30:21 +03:00
/*sql*/ ` UPDATE options
2024-12-22 15:42:15 +02:00
SET value = ?
2025-01-09 18:07:02 +02:00
WHERE name = ? ` ,
[ mig . dbVersion . toString ( ) , "dbVersion" ]
) ;
2024-02-17 19:42:30 +02:00
log . info ( ` Migration to version ${ mig . dbVersion } has been successful. ` ) ;
} catch ( e : any ) {
2025-03-02 19:59:50 +02:00
console . error ( e ) ;
2025-02-20 18:06:19 +02:00
crash ( t ( "migration.error_message" , { version : mig.dbVersion , stack : e.stack } ) ) ;
2024-07-18 23:26:21 +03:00
break ; // crash() is sometimes async
2024-02-17 19:42:30 +02:00
}
}
} ) ;
if ( currentDbVersion === 214 ) {
// special VACUUM after the big migration
log . info ( "VACUUMing database, this might take a while ..." ) ;
sql . execute ( "VACUUM" ) ;
}
}
2025-03-02 20:43:01 +02:00
async function prepareMigrations ( currentDbVersion : number ) : Promise < MigrationInfo [ ] > {
2025-05-30 21:32:35 +03:00
MIGRATIONS . sort ( ( a , b ) = > a . version - b . version ) ;
2025-03-02 20:43:01 +02:00
const migrations : MigrationInfo [ ] = [ ] ;
2025-05-30 21:32:35 +03:00
for ( const migration of MIGRATIONS ) {
const dbVersion = migration . version ;
2025-03-02 20:43:01 +02:00
if ( dbVersion > currentDbVersion ) {
2025-05-30 21:32:35 +03:00
if ( "sql" in migration ) {
migrations . push ( {
dbVersion ,
migration : migration.sql
} ) ;
} else {
2025-03-02 20:43:01 +02:00
// Due to ESM imports, the migration file needs to be imported asynchronously and thus cannot be loaded at migration time (since migration is not asynchronous).
// As such we have to preload the ESM.
2025-05-30 21:32:35 +03:00
migrations . push ( {
dbVersion ,
migration : ( await migration . module ( ) ) . default
} ) ;
2025-03-02 20:43:01 +02:00
}
}
}
return migrations ;
}
2025-05-30 21:32:35 +03:00
function executeMigration ( { migration } : MigrationInfo ) {
if ( typeof migration === "string" ) {
console . log ( ` Migration with SQL script: ${ migration } ` ) ;
sql . executeScript ( migration ) ;
2024-02-17 19:42:30 +02:00
} else {
2025-05-30 21:32:35 +03:00
console . log ( "Migration with JS module" ) ;
migration ( ) ;
} ;
2024-02-17 19:42:30 +02:00
}
function getDbVersion() {
return parseInt ( sql . getValue ( "SELECT value FROM options WHERE name = 'dbVersion'" ) ) ;
}
function isDbUpToDate() {
const dbVersion = getDbVersion ( ) ;
const upToDate = dbVersion >= appInfo . dbVersion ;
if ( ! upToDate ) {
log . info ( ` App db version is ${ appInfo . dbVersion } , while db version is ${ dbVersion } . Migration needed. ` ) ;
}
return upToDate ;
}
async function migrateIfNecessary() {
const currentDbVersion = getDbVersion ( ) ;
2025-01-09 18:07:02 +02:00
if ( currentDbVersion > appInfo . dbVersion && process . env . TRILIUM_IGNORE_DB_VERSION !== "true" ) {
2025-02-20 18:06:19 +02:00
await crash ( t ( "migration.wrong_db_version" , { version : currentDbVersion , targetVersion : appInfo.dbVersion } ) ) ;
2024-02-17 19:42:30 +02:00
}
if ( ! isDbUpToDate ( ) ) {
await migrate ( ) ;
}
}
2024-07-18 21:47:30 +03:00
export default {
2024-02-17 19:42:30 +02:00
migrateIfNecessary ,
isDbUpToDate
} ;