forked from appsemble/appsemble
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmigrate.ts
More file actions
102 lines (95 loc) · 3.71 KB
/
migrate.ts
File metadata and controls
102 lines (95 loc) · 3.71 KB
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
import { AppsembleError, logger } from '@appsemble/node-utils';
import semver from 'semver';
import { type Sequelize, type Transaction } from 'sequelize';
import { type Promisable } from 'type-fest';
import { type Patch } from './yaml.js';
import { getDB, Meta } from '../models/index.js';
export interface Migration {
key: string;
up: (transaction: Transaction, db: Sequelize) => Promisable<void>;
down: (transaction: Transaction, db: Sequelize) => Promisable<void>;
appPatches?: Patch[];
}
async function handleMigration(
db: Sequelize,
migration: Migration,
type: 'down' | 'up',
): Promise<void> {
try {
await db.transaction(async (t: Transaction) => {
await migration[type](t, db);
});
} catch (error) {
const [meta] = await Meta.findAll();
const prefix = type === 'up' ? 'Upgrade to' : 'Downgrade from';
if (!meta) {
logger.warn(
`${prefix} ${migration.key} unsuccessful, not committing. Please make sure to start from an empty database.`,
);
throw error;
}
logger.warn(
`${prefix} ${migration.key} unsuccessful, not committing. Current database version ${meta.version}.`,
);
logger.warn(
`In case this occurred on a hosted Appsemble instance,
and the logs above do not contain warnings to resolve the below error manually,
consider contacting \`support@appsemble.com\` to report the migration issue,
and include the stacktrace.`,
);
throw error;
}
}
export async function migrate(toVersion: string, migrations: Migration[]): Promise<void> {
const db = getDB();
await Meta.sync();
const to = toVersion === 'next' ? migrations.at(-1)!.key : toVersion;
const metas = await Meta.findAll();
if (metas.length > 1) {
throw new AppsembleError('Multiple Meta entries found. The database requires a manual fix.');
}
let meta: Meta;
if (metas.length === 0) {
logger.warn('No old database meta information was found.');
const [first, ...migrationsToApply] = migrations.filter(({ key }) => semver.lte(key, to));
logger.info(`Upgrade to ${first.key} started`);
await handleMigration(db, first, 'up');
meta = await Meta.create({ version: first.key });
for (const migration of migrationsToApply) {
logger.info(`Upgrade to ${migration.key} started`);
await handleMigration(db, migration, 'up');
[, [meta]] = await Meta.update({ version: migration.key }, { returning: true, where: {} });
logger.info(`Upgrade to ${migration.key} successful`);
}
return;
}
[meta] = metas;
if (semver.eq(to, meta.version)) {
logger.info(`Database is already on version ${to}. Nothing to migrate.`);
return;
}
logger.info(`Current database version: ${meta.version}`);
if (semver.gt(to, meta.version)) {
const migrationsToApply = migrations.filter(
({ key }) => semver.gt(key, meta.version) && semver.lte(key, to),
);
for (const migration of migrationsToApply) {
logger.info(`Upgrade to ${migration.key} started`);
await handleMigration(db, migration, 'up');
await Meta.update({ version: migration.key }, { where: {} });
logger.info(`Upgrade to ${migration.key} successful`);
}
} else {
const migrationsToApply = migrations
.filter(({ key }) => semver.lte(key, meta.version) && semver.gt(key, to))
.reverse();
for (const migration of migrationsToApply) {
logger.info(`Downgrade from ${migration.key} started`);
const migrationIndex = migrations.lastIndexOf(migration);
const version = migrationIndex ? migrations[migrationIndex - 1].key : '0.0.0';
await handleMigration(db, migration, 'down');
await Meta.update({ version }, { where: {} });
logger.info(`Downgrade from ${migration.key} successful`);
}
}
}