File size: 3,521 Bytes
0e5ff83 2dae031 31daf3d bb6f8cb 31daf3d dae4e82 2dae031 aa54575 0e5ff83 2dae031 06e807a 2dae031 aa54575 2dae031 31daf3d 2dae031 dae4e82 2dae031 06e807a 2dae031 dae4e82 2dae031 31daf3d 2dae031 31daf3d 2dae031 e9406a3 2dae031 e9406a3 06e807a 2dae031 31daf3d 2dae031 06e807a 2dae031 e9406a3 06e807a e9406a3 2dae031 aa54575 2dae031 aa54575 2dae031 aa54575 2dae031 06e807a bb6f8cb 2dae031 aa54575 2dae031 aa54575 2dae031 06e807a 2dae031 31daf3d 2dae031 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
import { Database } from "$lib/server/database";
import { migrations } from "./routines";
import { acquireLock, releaseLock, isDBLocked, refreshLock } from "./lock";
import { Semaphores } from "$lib/types/Semaphore";
import { logger } from "$lib/server/logger";
import { config } from "$lib/server/config";
export async function checkAndRunMigrations() {
// make sure all GUIDs are unique
if (new Set(migrations.map((m) => m._id.toString())).size !== migrations.length) {
throw new Error("Duplicate migration GUIDs found.");
}
// check if all migrations have already been run
const migrationResults = await (await Database.getInstance())
.getCollections()
.migrationResults.find()
.toArray();
logger.debug("[MIGRATIONS] Begin check...");
// connect to the database
const connectedClient = await (await Database.getInstance()).getClient().connect();
const lockId = await acquireLock(Semaphores.MIGRATION);
if (!lockId) {
// another instance already has the lock, so we exit early
logger.debug(
"[MIGRATIONS] Another instance already has the lock. Waiting for DB to be unlocked."
);
// Todo: is this necessary? Can we just return?
// block until the lock is released
while (await isDBLocked(Semaphores.MIGRATION)) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return;
}
// once here, we have the lock
// make sure to refresh it regularly while it's running
const refreshInterval = setInterval(async () => {
await refreshLock(Semaphores.MIGRATION, lockId);
}, 1000 * 10);
// iterate over all migrations
for (const migration of migrations) {
// check if the migration has already been applied
const shouldRun =
migration.runEveryTime ||
!migrationResults.find((m) => m._id.toString() === migration._id.toString());
// check if the migration has already been applied
if (!shouldRun) {
logger.debug(`[MIGRATIONS] "${migration.name}" already applied. Skipping...`);
} else {
// check the modifiers to see if some cases match
if (
(migration.runForHuggingChat === "only" && !config.isHuggingChat) ||
(migration.runForHuggingChat === "never" && config.isHuggingChat)
) {
logger.debug(
`[MIGRATIONS] "${migration.name}" should not be applied for this run. Skipping...`
);
continue;
}
// otherwise all is good and we can run the migration
logger.debug(
`[MIGRATIONS] "${migration.name}" ${
migration.runEveryTime ? "should run every time" : "not applied yet"
}. Applying...`
);
await (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(await Database.getInstance());
});
} catch (e) {
logger.debug(`[MIGRATIONS] "${migration.name}" failed!`);
logger.error(e);
} finally {
await session.endSession();
}
await (await Database.getInstance()).getCollections().migrationResults.updateOne(
{ _id: migration._id },
{
$set: {
name: migration.name,
status: result ? "success" : "failure",
},
},
{ upsert: true }
);
}
}
logger.debug("[MIGRATIONS] All migrations applied. Releasing lock");
clearInterval(refreshInterval);
await releaseLock(Semaphores.MIGRATION, lockId);
}
|