| import { Database } from "$lib/server/database"; |
| import { migrations } from "./routines"; |
| import { acquireLock, releaseLock, isDBLocked, refreshLock } from "./lock"; |
| import { isHuggingChat } from "$lib/utils/isHuggingChat"; |
| import { logger } from "$lib/server/logger"; |
|
|
| const LOCK_KEY = "migrations"; |
|
|
| export async function checkAndRunMigrations() { |
| |
| if (new Set(migrations.map((m) => m._id.toString())).size !== migrations.length) { |
| throw new Error("Duplicate migration GUIDs found."); |
| } |
|
|
| |
| const migrationResults = await Database.getInstance() |
| .getCollections() |
| .migrationResults.find() |
| .toArray(); |
|
|
| logger.info("[MIGRATIONS] Begin check..."); |
|
|
| |
| const connectedClient = await Database.getInstance().getClient().connect(); |
|
|
| const lockId = await acquireLock(LOCK_KEY); |
|
|
| if (!lockId) { |
| |
| logger.info( |
| "[MIGRATIONS] Another instance already has the lock. Waiting for DB to be unlocked." |
| ); |
|
|
| |
| |
| while (await isDBLocked(LOCK_KEY)) { |
| await new Promise((resolve) => setTimeout(resolve, 1000)); |
| } |
| return; |
| } |
|
|
| |
| |
| const refreshInterval = setInterval(async () => { |
| await refreshLock(LOCK_KEY, lockId); |
| }, 1000 * 10); |
|
|
| |
| for (const migration of migrations) { |
| |
| const shouldRun = |
| migration.runEveryTime || |
| !migrationResults.find((m) => m._id.toString() === migration._id.toString()); |
|
|
| |
| if (!shouldRun) { |
| logger.info(`[MIGRATIONS] "${migration.name}" already applied. Skipping...`); |
| } else { |
| |
| if ( |
| (migration.runForHuggingChat === "only" && !isHuggingChat) || |
| (migration.runForHuggingChat === "never" && isHuggingChat) |
| ) { |
| logger.info( |
| `[MIGRATIONS] "${migration.name}" should not be applied for this run. Skipping...` |
| ); |
| continue; |
| } |
|
|
| |
| logger.info( |
| `[MIGRATIONS] "${migration.name}" ${ |
| migration.runEveryTime ? "should run every time" : "not applied yet" |
| }. Applying...` |
| ); |
|
|
| await Database.getInstance() |
| .getCollections() |
| .migrationResults.updateOne( |
| { _id: migration._id }, |
| { |
| $set: { |
| name: migration.name, |
| status: "ongoing", |
| }, |
| }, |
| { upsert: true } |
| ); |
|
|
| const session = connectedClient.startSession(); |
| let result = false; |
|
|
| try { |
| await session.withTransaction(async () => { |
| result = await migration.up(Database.getInstance()); |
| }); |
| } catch (e) { |
| logger.info(`[MIGRATIONS] "${migration.name}" failed!`); |
| logger.error(e); |
| } finally { |
| await session.endSession(); |
| } |
|
|
| await Database.getInstance() |
| .getCollections() |
| .migrationResults.updateOne( |
| { _id: migration._id }, |
| { |
| $set: { |
| name: migration.name, |
| status: result ? "success" : "failure", |
| }, |
| }, |
| { upsert: true } |
| ); |
| } |
| } |
|
|
| logger.info("[MIGRATIONS] All migrations applied. Releasing lock"); |
|
|
| clearInterval(refreshInterval); |
| await releaseLock(LOCK_KEY, lockId); |
| } |
|
|