@sqb/migrator 4.10.1 → 4.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/adapters/pg-migration-adapter.js +26 -12
- package/cjs/db-migrator.js +29 -7
- package/cjs/migration-package.js +22 -12
- package/esm/adapters/pg-migration-adapter.js +25 -12
- package/esm/db-migrator.js +29 -7
- package/esm/migration-package.js +22 -12
- package/package.json +4 -4
- package/types/adapters/pg-migration-adapter.d.ts +7 -4
- package/types/db-migrator.d.ts +2 -2
- package/types/migration-adapter.d.ts +5 -3
- package/types/migration-package.d.ts +9 -11
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PgMigrationAdapter = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
4
6
|
const postgresql_client_1 = require("postgresql-client");
|
|
5
7
|
const postgres_1 = require("@sqb/postgres");
|
|
6
8
|
const migration_adapter_js_1 = require("../migration-adapter.js");
|
|
@@ -11,7 +13,6 @@ class PgMigrationAdapter extends migration_adapter_js_1.MigrationAdapter {
|
|
|
11
13
|
constructor() {
|
|
12
14
|
super(...arguments);
|
|
13
15
|
this._infoSchema = 'public';
|
|
14
|
-
this._packageName = '';
|
|
15
16
|
this._version = 0;
|
|
16
17
|
this._status = types_js_1.MigrationStatus.idle;
|
|
17
18
|
this.defaultVariables = {
|
|
@@ -23,7 +24,7 @@ class PgMigrationAdapter extends migration_adapter_js_1.MigrationAdapter {
|
|
|
23
24
|
this.eventTable = 'migration_events';
|
|
24
25
|
}
|
|
25
26
|
get packageName() {
|
|
26
|
-
return this.
|
|
27
|
+
return this._migrationPackage.name;
|
|
27
28
|
}
|
|
28
29
|
get version() {
|
|
29
30
|
return this._version;
|
|
@@ -46,6 +47,7 @@ class PgMigrationAdapter extends migration_adapter_js_1.MigrationAdapter {
|
|
|
46
47
|
try {
|
|
47
48
|
const adapter = new PgMigrationAdapter();
|
|
48
49
|
adapter._connection = connection;
|
|
50
|
+
adapter._migrationPackage = options.migrationPackage;
|
|
49
51
|
adapter._infoSchema = options.infoSchema || '__migration';
|
|
50
52
|
adapter.defaultVariables.schema = options.connection.schema || '';
|
|
51
53
|
if (!adapter.defaultVariables.schema) {
|
|
@@ -74,7 +76,9 @@ CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
|
74
76
|
version integer not null default 0,
|
|
75
77
|
event varchar(16) not null,
|
|
76
78
|
event_time timestamp without time zone not null,
|
|
79
|
+
title text,
|
|
77
80
|
message text not null,
|
|
81
|
+
filename text,
|
|
78
82
|
details text,
|
|
79
83
|
CONSTRAINT pk_${adapter.eventTable} PRIMARY KEY (id)
|
|
80
84
|
)`);
|
|
@@ -108,14 +112,16 @@ CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
|
108
112
|
const params = [];
|
|
109
113
|
if (info.status && info.status !== this.status) {
|
|
110
114
|
params.push(info.status);
|
|
111
|
-
sql += '
|
|
115
|
+
sql += ',\n status = $' + (params.length);
|
|
112
116
|
}
|
|
113
117
|
if (info.version && info.version !== this.version) {
|
|
114
118
|
params.push(info.version);
|
|
115
|
-
sql += '
|
|
119
|
+
sql += ',\n current_version = $' + (params.length);
|
|
116
120
|
}
|
|
117
121
|
if (sql) {
|
|
118
|
-
|
|
122
|
+
params.push(this.packageName);
|
|
123
|
+
sql = `update ${this.summaryTableFull} set updated_at = current_timestamp` + sql +
|
|
124
|
+
`\n where package_name =$` + (params.length);
|
|
119
125
|
await this._connection.query(sql, { params });
|
|
120
126
|
if (info.status)
|
|
121
127
|
this._status = info.status;
|
|
@@ -125,30 +131,38 @@ CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
|
125
131
|
}
|
|
126
132
|
async writeEvent(event) {
|
|
127
133
|
const sql = `insert into ${this.eventTableFull} ` +
|
|
128
|
-
'(package_name, version, event, event_time, message, details) ' +
|
|
129
|
-
'values ($1, $2, $3, CURRENT_TIMESTAMP, $4, $5)';
|
|
134
|
+
'(package_name, version, event, event_time, title, message, filename, details) ' +
|
|
135
|
+
'values ($1, $2, $3, CURRENT_TIMESTAMP, $4, $5, $6, $7)';
|
|
130
136
|
await this._connection.query(sql, {
|
|
131
137
|
params: [
|
|
132
138
|
this.packageName, event.version, event.event,
|
|
133
|
-
event.message, event.details
|
|
139
|
+
event.title, event.message, event.filename, event.details
|
|
134
140
|
]
|
|
135
141
|
});
|
|
136
142
|
}
|
|
137
|
-
async executeTask(task, variables) {
|
|
143
|
+
async executeTask(migrationPackage, migration, task, variables) {
|
|
138
144
|
variables = {
|
|
139
145
|
...this.defaultVariables,
|
|
140
146
|
...variables
|
|
141
147
|
};
|
|
142
148
|
if ((0, migration_package_js_1.isSqlScriptMigrationTask)(task)) {
|
|
143
149
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
let script;
|
|
151
|
+
if (typeof task.script === 'function') {
|
|
152
|
+
script = await task.script({ migrationPackage, migration, task, variables });
|
|
153
|
+
}
|
|
154
|
+
else
|
|
155
|
+
script = task.script;
|
|
156
|
+
if (typeof script !== 'string')
|
|
157
|
+
return;
|
|
158
|
+
script = script
|
|
159
|
+
.replace(/(\$\((\w+)\))/g, (s, ...args) => variables[args[1]] || s);
|
|
146
160
|
await this._connection.execute(script);
|
|
147
161
|
}
|
|
148
162
|
catch (e) {
|
|
149
163
|
let msg = `Error in task "${task.title}"`;
|
|
150
164
|
if (task.filename)
|
|
151
|
-
msg += '\n at ' + task.filename;
|
|
165
|
+
msg += '\n at ' + path_1.default.relative(migrationPackage.baseDir, task.filename);
|
|
152
166
|
if (e.lineNr) {
|
|
153
167
|
if (!task.filename)
|
|
154
168
|
e.message += '\n at';
|
package/cjs/db-migrator.js
CHANGED
|
@@ -25,7 +25,7 @@ class DbMigrator extends strict_typed_events_1.AsyncEventEmitter {
|
|
|
25
25
|
let migrationAdapter;
|
|
26
26
|
switch (options.connection.dialect) {
|
|
27
27
|
case 'postgres': {
|
|
28
|
-
migrationAdapter = await pg_migration_adapter_js_1.PgMigrationAdapter.create(options);
|
|
28
|
+
migrationAdapter = await pg_migration_adapter_js_1.PgMigrationAdapter.create({ ...options, migrationPackage });
|
|
29
29
|
break;
|
|
30
30
|
}
|
|
31
31
|
default:
|
|
@@ -49,41 +49,63 @@ class DbMigrator extends strict_typed_events_1.AsyncEventEmitter {
|
|
|
49
49
|
await migrationAdapter.backupDatabase();
|
|
50
50
|
}
|
|
51
51
|
// Execute migration tasks
|
|
52
|
+
let migrationIndex = -1;
|
|
52
53
|
for (const migration of migrations) {
|
|
54
|
+
migrationIndex++;
|
|
53
55
|
if (migration.version > targetVersion || migrationAdapter.version >= migration.version)
|
|
54
56
|
continue;
|
|
57
|
+
await this.emitAsync('migration-start', {
|
|
58
|
+
migration,
|
|
59
|
+
total: migrations.length,
|
|
60
|
+
index: migrationIndex
|
|
61
|
+
});
|
|
55
62
|
for (let index = 0; index < migration.tasks.length; index++) {
|
|
56
63
|
task = migration.tasks[index];
|
|
57
|
-
await this.emitAsync('task-start', {
|
|
64
|
+
await this.emitAsync('task-start', { migration, task, total, index });
|
|
58
65
|
await migrationAdapter.update({ status: types_js_1.MigrationStatus.busy });
|
|
59
66
|
await migrationAdapter.writeEvent({
|
|
60
67
|
event: migration_adapter_js_1.MigrationAdapter.EventKind.started,
|
|
61
68
|
version: migration.version,
|
|
69
|
+
title: task.title,
|
|
70
|
+
filename: task.filename,
|
|
62
71
|
message: `Task "${task.title}" started`
|
|
63
72
|
});
|
|
64
73
|
try {
|
|
65
|
-
await migrationAdapter.executeTask(task, {
|
|
74
|
+
await migrationAdapter.executeTask(migrationPackage, migration, task, {
|
|
66
75
|
schema: options.connection.schema,
|
|
67
76
|
...options.scriptVariables,
|
|
68
77
|
});
|
|
69
78
|
await migrationAdapter.writeEvent({
|
|
70
79
|
event: migration_adapter_js_1.MigrationAdapter.EventKind.success,
|
|
71
80
|
version: migration.version,
|
|
72
|
-
|
|
81
|
+
title: task.title,
|
|
82
|
+
filename: task.filename,
|
|
83
|
+
message: `Task "${task.title}" completed`,
|
|
73
84
|
});
|
|
74
85
|
}
|
|
75
86
|
catch (e) {
|
|
76
87
|
await migrationAdapter.writeEvent({
|
|
77
88
|
event: migration_adapter_js_1.MigrationAdapter.EventKind.error,
|
|
78
89
|
version: migration.version,
|
|
79
|
-
|
|
90
|
+
title: task.title,
|
|
91
|
+
filename: task.filename,
|
|
92
|
+
message: String(e),
|
|
93
|
+
details: e.message + '\n\n' +
|
|
94
|
+
Object.keys(e)
|
|
95
|
+
.filter(k => e[k] != null)
|
|
96
|
+
.map(k => k + ': ' + e[k]).join('\n')
|
|
80
97
|
});
|
|
81
98
|
// noinspection ExceptionCaughtLocallyJS
|
|
82
99
|
throw e;
|
|
83
100
|
}
|
|
84
|
-
await this.emitAsync('task-finish', {
|
|
101
|
+
await this.emitAsync('task-finish', { migration, task, total, index });
|
|
85
102
|
}
|
|
86
|
-
await migrationAdapter.update({ version: migration.version });
|
|
103
|
+
await migrationAdapter.update({ version: migration.version, status: types_js_1.MigrationStatus.idle });
|
|
104
|
+
await this.emitAsync('migration-finish', {
|
|
105
|
+
migration,
|
|
106
|
+
total: migrations.length,
|
|
107
|
+
index: migrationIndex
|
|
108
|
+
});
|
|
87
109
|
}
|
|
88
110
|
}
|
|
89
111
|
catch (e) {
|
package/cjs/migration-package.js
CHANGED
|
@@ -7,7 +7,8 @@ const promises_1 = tslib_1.__importDefault(require("fs/promises"));
|
|
|
7
7
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
8
8
|
const get_calling_filename_js_1 = require("./utils/get-calling-filename.js");
|
|
9
9
|
function isSqlScriptMigrationTask(x) {
|
|
10
|
-
return typeof x === 'object' &&
|
|
10
|
+
return typeof x === 'object' &&
|
|
11
|
+
(typeof x.script === 'string' || typeof x.script === 'function');
|
|
11
12
|
}
|
|
12
13
|
exports.isSqlScriptMigrationTask = isSqlScriptMigrationTask;
|
|
13
14
|
function isInsertDataMigrationTask(x) {
|
|
@@ -24,13 +25,14 @@ exports.isCustomMigrationTask = isCustomMigrationTask;
|
|
|
24
25
|
var MigrationPackage;
|
|
25
26
|
(function (MigrationPackage) {
|
|
26
27
|
async function load(asyncConfig) {
|
|
28
|
+
const baseDir = asyncConfig.baseDir || path_1.default.dirname((0, get_calling_filename_js_1.getCallingFilename)(1));
|
|
27
29
|
const out = {
|
|
28
30
|
...asyncConfig,
|
|
31
|
+
baseDir,
|
|
29
32
|
migrations: []
|
|
30
33
|
};
|
|
31
34
|
if (!Array.isArray(asyncConfig.migrations))
|
|
32
35
|
throw new TypeError('You must provide array of MigrationConfig in "migrations" property');
|
|
33
|
-
const baseDir = path_1.default.dirname((0, get_calling_filename_js_1.getCallingFilename)(1));
|
|
34
36
|
if (asyncConfig.migrations?.length) {
|
|
35
37
|
const srcMigrations = [];
|
|
36
38
|
const trgMigrations = [];
|
|
@@ -41,12 +43,12 @@ var MigrationPackage;
|
|
|
41
43
|
if (typeof x === 'object' && x.tasks)
|
|
42
44
|
srcMigrations.push(x);
|
|
43
45
|
else if (typeof x === 'string') {
|
|
44
|
-
srcMigrations.push(...await loadMigrations(
|
|
46
|
+
srcMigrations.push(...await loadMigrations(baseDir, x.replace(/\\/g, '/')));
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
srcMigrations.sort((a, b) => a.version - b.version);
|
|
48
50
|
for (const migration of srcMigrations) {
|
|
49
|
-
const trgMigration = { ...migration, tasks: [] };
|
|
51
|
+
const trgMigration = { baseDir: '', ...migration, tasks: [] };
|
|
50
52
|
trgMigrations.push(trgMigration);
|
|
51
53
|
const srcTasks = migration.tasks;
|
|
52
54
|
trgMigration.tasks = [];
|
|
@@ -56,8 +58,12 @@ var MigrationPackage;
|
|
|
56
58
|
}
|
|
57
59
|
else if (typeof t === 'string') {
|
|
58
60
|
let pattern = t.replace(/\\/g, '/');
|
|
59
|
-
pattern = path_1.default.resolve(
|
|
60
|
-
const files = await (0, fast_glob_1.default)(pattern, {
|
|
61
|
+
pattern = path_1.default.resolve(path_1.default.join(baseDir, trgMigration.baseDir, pattern));
|
|
62
|
+
const files = await (0, fast_glob_1.default)(pattern, {
|
|
63
|
+
absolute: true,
|
|
64
|
+
onlyFiles: true
|
|
65
|
+
});
|
|
66
|
+
files.sort();
|
|
61
67
|
for (const filename of files) {
|
|
62
68
|
const ext = path_1.default.extname(filename).toLowerCase();
|
|
63
69
|
if (!path_1.default.basename(filename, ext).endsWith('.task'))
|
|
@@ -65,16 +71,20 @@ var MigrationPackage;
|
|
|
65
71
|
if (ext === '.sql') {
|
|
66
72
|
const script = await promises_1.default.readFile(filename, 'utf-8');
|
|
67
73
|
trgMigration.tasks.push({
|
|
68
|
-
title: path_1.default.basename(filename),
|
|
74
|
+
title: path_1.default.basename(filename, ext),
|
|
69
75
|
filename,
|
|
70
76
|
script
|
|
71
77
|
});
|
|
72
78
|
}
|
|
73
|
-
else if (
|
|
79
|
+
else if (['.json', '.js', '.ts', '.cjs', '.mjs'].includes(ext)) {
|
|
74
80
|
try {
|
|
75
|
-
|
|
81
|
+
let json = ext === '.json'
|
|
82
|
+
? JSON.parse(await promises_1.default.readFile(filename, 'utf-8'))
|
|
83
|
+
: await Promise.resolve(`${filename}`).then(s => tslib_1.__importStar(require(s)));
|
|
76
84
|
if (typeof json !== 'object')
|
|
77
85
|
continue;
|
|
86
|
+
if (json.__esModule)
|
|
87
|
+
json = json.default;
|
|
78
88
|
if (json.script) {
|
|
79
89
|
json.title = json.title || 'Run sql script';
|
|
80
90
|
json.filename = filename;
|
|
@@ -106,9 +116,9 @@ var MigrationPackage;
|
|
|
106
116
|
return out;
|
|
107
117
|
}
|
|
108
118
|
MigrationPackage.load = load;
|
|
109
|
-
async function loadMigrations(pattern) {
|
|
119
|
+
async function loadMigrations(baseDir, pattern) {
|
|
110
120
|
const out = [];
|
|
111
|
-
const files = await (0, fast_glob_1.default)(pattern, { absolute: true, onlyFiles: true });
|
|
121
|
+
const files = await (0, fast_glob_1.default)(path_1.default.join(baseDir, pattern), { absolute: true, onlyFiles: true });
|
|
112
122
|
for (const filename of files) {
|
|
113
123
|
const ext = path_1.default.extname(filename).toLowerCase();
|
|
114
124
|
if (path_1.default.basename(filename, ext) !== 'migration')
|
|
@@ -129,7 +139,7 @@ var MigrationPackage;
|
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
if (json && typeof json === 'object' && json.version && Array.isArray(json.tasks)) {
|
|
132
|
-
json.
|
|
142
|
+
json.baseDir = path_1.default.relative(baseDir, path_1.default.dirname(filename));
|
|
133
143
|
out.push(json);
|
|
134
144
|
}
|
|
135
145
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { stringifyValueForSQL } from 'postgresql-client';
|
|
2
3
|
import { PgAdapter } from '@sqb/postgres';
|
|
3
4
|
import { MigrationAdapter } from '../migration-adapter.js';
|
|
@@ -8,7 +9,6 @@ export class PgMigrationAdapter extends MigrationAdapter {
|
|
|
8
9
|
constructor() {
|
|
9
10
|
super(...arguments);
|
|
10
11
|
this._infoSchema = 'public';
|
|
11
|
-
this._packageName = '';
|
|
12
12
|
this._version = 0;
|
|
13
13
|
this._status = MigrationStatus.idle;
|
|
14
14
|
this.defaultVariables = {
|
|
@@ -20,7 +20,7 @@ export class PgMigrationAdapter extends MigrationAdapter {
|
|
|
20
20
|
this.eventTable = 'migration_events';
|
|
21
21
|
}
|
|
22
22
|
get packageName() {
|
|
23
|
-
return this.
|
|
23
|
+
return this._migrationPackage.name;
|
|
24
24
|
}
|
|
25
25
|
get version() {
|
|
26
26
|
return this._version;
|
|
@@ -43,6 +43,7 @@ export class PgMigrationAdapter extends MigrationAdapter {
|
|
|
43
43
|
try {
|
|
44
44
|
const adapter = new PgMigrationAdapter();
|
|
45
45
|
adapter._connection = connection;
|
|
46
|
+
adapter._migrationPackage = options.migrationPackage;
|
|
46
47
|
adapter._infoSchema = options.infoSchema || '__migration';
|
|
47
48
|
adapter.defaultVariables.schema = options.connection.schema || '';
|
|
48
49
|
if (!adapter.defaultVariables.schema) {
|
|
@@ -71,7 +72,9 @@ CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
|
71
72
|
version integer not null default 0,
|
|
72
73
|
event varchar(16) not null,
|
|
73
74
|
event_time timestamp without time zone not null,
|
|
75
|
+
title text,
|
|
74
76
|
message text not null,
|
|
77
|
+
filename text,
|
|
75
78
|
details text,
|
|
76
79
|
CONSTRAINT pk_${adapter.eventTable} PRIMARY KEY (id)
|
|
77
80
|
)`);
|
|
@@ -105,14 +108,16 @@ CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
|
105
108
|
const params = [];
|
|
106
109
|
if (info.status && info.status !== this.status) {
|
|
107
110
|
params.push(info.status);
|
|
108
|
-
sql += '
|
|
111
|
+
sql += ',\n status = $' + (params.length);
|
|
109
112
|
}
|
|
110
113
|
if (info.version && info.version !== this.version) {
|
|
111
114
|
params.push(info.version);
|
|
112
|
-
sql += '
|
|
115
|
+
sql += ',\n current_version = $' + (params.length);
|
|
113
116
|
}
|
|
114
117
|
if (sql) {
|
|
115
|
-
|
|
118
|
+
params.push(this.packageName);
|
|
119
|
+
sql = `update ${this.summaryTableFull} set updated_at = current_timestamp` + sql +
|
|
120
|
+
`\n where package_name =$` + (params.length);
|
|
116
121
|
await this._connection.query(sql, { params });
|
|
117
122
|
if (info.status)
|
|
118
123
|
this._status = info.status;
|
|
@@ -122,30 +127,38 @@ CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
|
122
127
|
}
|
|
123
128
|
async writeEvent(event) {
|
|
124
129
|
const sql = `insert into ${this.eventTableFull} ` +
|
|
125
|
-
'(package_name, version, event, event_time, message, details) ' +
|
|
126
|
-
'values ($1, $2, $3, CURRENT_TIMESTAMP, $4, $5)';
|
|
130
|
+
'(package_name, version, event, event_time, title, message, filename, details) ' +
|
|
131
|
+
'values ($1, $2, $3, CURRENT_TIMESTAMP, $4, $5, $6, $7)';
|
|
127
132
|
await this._connection.query(sql, {
|
|
128
133
|
params: [
|
|
129
134
|
this.packageName, event.version, event.event,
|
|
130
|
-
event.message, event.details
|
|
135
|
+
event.title, event.message, event.filename, event.details
|
|
131
136
|
]
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
|
-
async executeTask(task, variables) {
|
|
139
|
+
async executeTask(migrationPackage, migration, task, variables) {
|
|
135
140
|
variables = {
|
|
136
141
|
...this.defaultVariables,
|
|
137
142
|
...variables
|
|
138
143
|
};
|
|
139
144
|
if (isSqlScriptMigrationTask(task)) {
|
|
140
145
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
let script;
|
|
147
|
+
if (typeof task.script === 'function') {
|
|
148
|
+
script = await task.script({ migrationPackage, migration, task, variables });
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
script = task.script;
|
|
152
|
+
if (typeof script !== 'string')
|
|
153
|
+
return;
|
|
154
|
+
script = script
|
|
155
|
+
.replace(/(\$\((\w+)\))/g, (s, ...args) => variables[args[1]] || s);
|
|
143
156
|
await this._connection.execute(script);
|
|
144
157
|
}
|
|
145
158
|
catch (e) {
|
|
146
159
|
let msg = `Error in task "${task.title}"`;
|
|
147
160
|
if (task.filename)
|
|
148
|
-
msg += '\n at ' + task.filename;
|
|
161
|
+
msg += '\n at ' + path.relative(migrationPackage.baseDir, task.filename);
|
|
149
162
|
if (e.lineNr) {
|
|
150
163
|
if (!task.filename)
|
|
151
164
|
e.message += '\n at';
|
package/esm/db-migrator.js
CHANGED
|
@@ -22,7 +22,7 @@ export class DbMigrator extends AsyncEventEmitter {
|
|
|
22
22
|
let migrationAdapter;
|
|
23
23
|
switch (options.connection.dialect) {
|
|
24
24
|
case 'postgres': {
|
|
25
|
-
migrationAdapter = await PgMigrationAdapter.create(options);
|
|
25
|
+
migrationAdapter = await PgMigrationAdapter.create({ ...options, migrationPackage });
|
|
26
26
|
break;
|
|
27
27
|
}
|
|
28
28
|
default:
|
|
@@ -46,41 +46,63 @@ export class DbMigrator extends AsyncEventEmitter {
|
|
|
46
46
|
await migrationAdapter.backupDatabase();
|
|
47
47
|
}
|
|
48
48
|
// Execute migration tasks
|
|
49
|
+
let migrationIndex = -1;
|
|
49
50
|
for (const migration of migrations) {
|
|
51
|
+
migrationIndex++;
|
|
50
52
|
if (migration.version > targetVersion || migrationAdapter.version >= migration.version)
|
|
51
53
|
continue;
|
|
54
|
+
await this.emitAsync('migration-start', {
|
|
55
|
+
migration,
|
|
56
|
+
total: migrations.length,
|
|
57
|
+
index: migrationIndex
|
|
58
|
+
});
|
|
52
59
|
for (let index = 0; index < migration.tasks.length; index++) {
|
|
53
60
|
task = migration.tasks[index];
|
|
54
|
-
await this.emitAsync('task-start', {
|
|
61
|
+
await this.emitAsync('task-start', { migration, task, total, index });
|
|
55
62
|
await migrationAdapter.update({ status: MigrationStatus.busy });
|
|
56
63
|
await migrationAdapter.writeEvent({
|
|
57
64
|
event: MigrationAdapter.EventKind.started,
|
|
58
65
|
version: migration.version,
|
|
66
|
+
title: task.title,
|
|
67
|
+
filename: task.filename,
|
|
59
68
|
message: `Task "${task.title}" started`
|
|
60
69
|
});
|
|
61
70
|
try {
|
|
62
|
-
await migrationAdapter.executeTask(task, {
|
|
71
|
+
await migrationAdapter.executeTask(migrationPackage, migration, task, {
|
|
63
72
|
schema: options.connection.schema,
|
|
64
73
|
...options.scriptVariables,
|
|
65
74
|
});
|
|
66
75
|
await migrationAdapter.writeEvent({
|
|
67
76
|
event: MigrationAdapter.EventKind.success,
|
|
68
77
|
version: migration.version,
|
|
69
|
-
|
|
78
|
+
title: task.title,
|
|
79
|
+
filename: task.filename,
|
|
80
|
+
message: `Task "${task.title}" completed`,
|
|
70
81
|
});
|
|
71
82
|
}
|
|
72
83
|
catch (e) {
|
|
73
84
|
await migrationAdapter.writeEvent({
|
|
74
85
|
event: MigrationAdapter.EventKind.error,
|
|
75
86
|
version: migration.version,
|
|
76
|
-
|
|
87
|
+
title: task.title,
|
|
88
|
+
filename: task.filename,
|
|
89
|
+
message: String(e),
|
|
90
|
+
details: e.message + '\n\n' +
|
|
91
|
+
Object.keys(e)
|
|
92
|
+
.filter(k => e[k] != null)
|
|
93
|
+
.map(k => k + ': ' + e[k]).join('\n')
|
|
77
94
|
});
|
|
78
95
|
// noinspection ExceptionCaughtLocallyJS
|
|
79
96
|
throw e;
|
|
80
97
|
}
|
|
81
|
-
await this.emitAsync('task-finish', {
|
|
98
|
+
await this.emitAsync('task-finish', { migration, task, total, index });
|
|
82
99
|
}
|
|
83
|
-
await migrationAdapter.update({ version: migration.version });
|
|
100
|
+
await migrationAdapter.update({ version: migration.version, status: MigrationStatus.idle });
|
|
101
|
+
await this.emitAsync('migration-finish', {
|
|
102
|
+
migration,
|
|
103
|
+
total: migrations.length,
|
|
104
|
+
index: migrationIndex
|
|
105
|
+
});
|
|
84
106
|
}
|
|
85
107
|
}
|
|
86
108
|
catch (e) {
|
package/esm/migration-package.js
CHANGED
|
@@ -3,7 +3,8 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { getCallingFilename } from './utils/get-calling-filename.js';
|
|
5
5
|
export function isSqlScriptMigrationTask(x) {
|
|
6
|
-
return typeof x === 'object' &&
|
|
6
|
+
return typeof x === 'object' &&
|
|
7
|
+
(typeof x.script === 'string' || typeof x.script === 'function');
|
|
7
8
|
}
|
|
8
9
|
export function isInsertDataMigrationTask(x) {
|
|
9
10
|
return typeof x === 'object' &&
|
|
@@ -17,13 +18,14 @@ export function isCustomMigrationTask(x) {
|
|
|
17
18
|
export var MigrationPackage;
|
|
18
19
|
(function (MigrationPackage) {
|
|
19
20
|
async function load(asyncConfig) {
|
|
21
|
+
const baseDir = asyncConfig.baseDir || path.dirname(getCallingFilename(1));
|
|
20
22
|
const out = {
|
|
21
23
|
...asyncConfig,
|
|
24
|
+
baseDir,
|
|
22
25
|
migrations: []
|
|
23
26
|
};
|
|
24
27
|
if (!Array.isArray(asyncConfig.migrations))
|
|
25
28
|
throw new TypeError('You must provide array of MigrationConfig in "migrations" property');
|
|
26
|
-
const baseDir = path.dirname(getCallingFilename(1));
|
|
27
29
|
if (asyncConfig.migrations?.length) {
|
|
28
30
|
const srcMigrations = [];
|
|
29
31
|
const trgMigrations = [];
|
|
@@ -34,12 +36,12 @@ export var MigrationPackage;
|
|
|
34
36
|
if (typeof x === 'object' && x.tasks)
|
|
35
37
|
srcMigrations.push(x);
|
|
36
38
|
else if (typeof x === 'string') {
|
|
37
|
-
srcMigrations.push(...await loadMigrations(
|
|
39
|
+
srcMigrations.push(...await loadMigrations(baseDir, x.replace(/\\/g, '/')));
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
srcMigrations.sort((a, b) => a.version - b.version);
|
|
41
43
|
for (const migration of srcMigrations) {
|
|
42
|
-
const trgMigration = { ...migration, tasks: [] };
|
|
44
|
+
const trgMigration = { baseDir: '', ...migration, tasks: [] };
|
|
43
45
|
trgMigrations.push(trgMigration);
|
|
44
46
|
const srcTasks = migration.tasks;
|
|
45
47
|
trgMigration.tasks = [];
|
|
@@ -49,8 +51,12 @@ export var MigrationPackage;
|
|
|
49
51
|
}
|
|
50
52
|
else if (typeof t === 'string') {
|
|
51
53
|
let pattern = t.replace(/\\/g, '/');
|
|
52
|
-
pattern = path.resolve(
|
|
53
|
-
const files = await glob(pattern, {
|
|
54
|
+
pattern = path.resolve(path.join(baseDir, trgMigration.baseDir, pattern));
|
|
55
|
+
const files = await glob(pattern, {
|
|
56
|
+
absolute: true,
|
|
57
|
+
onlyFiles: true
|
|
58
|
+
});
|
|
59
|
+
files.sort();
|
|
54
60
|
for (const filename of files) {
|
|
55
61
|
const ext = path.extname(filename).toLowerCase();
|
|
56
62
|
if (!path.basename(filename, ext).endsWith('.task'))
|
|
@@ -58,16 +64,20 @@ export var MigrationPackage;
|
|
|
58
64
|
if (ext === '.sql') {
|
|
59
65
|
const script = await fs.readFile(filename, 'utf-8');
|
|
60
66
|
trgMigration.tasks.push({
|
|
61
|
-
title: path.basename(filename),
|
|
67
|
+
title: path.basename(filename, ext),
|
|
62
68
|
filename,
|
|
63
69
|
script
|
|
64
70
|
});
|
|
65
71
|
}
|
|
66
|
-
else if (
|
|
72
|
+
else if (['.json', '.js', '.ts', '.cjs', '.mjs'].includes(ext)) {
|
|
67
73
|
try {
|
|
68
|
-
|
|
74
|
+
let json = ext === '.json'
|
|
75
|
+
? JSON.parse(await fs.readFile(filename, 'utf-8'))
|
|
76
|
+
: await import(filename);
|
|
69
77
|
if (typeof json !== 'object')
|
|
70
78
|
continue;
|
|
79
|
+
if (json.__esModule)
|
|
80
|
+
json = json.default;
|
|
71
81
|
if (json.script) {
|
|
72
82
|
json.title = json.title || 'Run sql script';
|
|
73
83
|
json.filename = filename;
|
|
@@ -99,9 +109,9 @@ export var MigrationPackage;
|
|
|
99
109
|
return out;
|
|
100
110
|
}
|
|
101
111
|
MigrationPackage.load = load;
|
|
102
|
-
async function loadMigrations(pattern) {
|
|
112
|
+
async function loadMigrations(baseDir, pattern) {
|
|
103
113
|
const out = [];
|
|
104
|
-
const files = await glob(pattern, { absolute: true, onlyFiles: true });
|
|
114
|
+
const files = await glob(path.join(baseDir, pattern), { absolute: true, onlyFiles: true });
|
|
105
115
|
for (const filename of files) {
|
|
106
116
|
const ext = path.extname(filename).toLowerCase();
|
|
107
117
|
if (path.basename(filename, ext) !== 'migration')
|
|
@@ -122,7 +132,7 @@ export var MigrationPackage;
|
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
134
|
if (json && typeof json === 'object' && json.version && Array.isArray(json.tasks)) {
|
|
125
|
-
json.
|
|
135
|
+
json.baseDir = path.relative(baseDir, path.dirname(filename));
|
|
126
136
|
out.push(json);
|
|
127
137
|
}
|
|
128
138
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqb/migrator",
|
|
3
3
|
"description": "Database migrator for SQB",
|
|
4
|
-
"version": "4.10.
|
|
4
|
+
"version": "4.10.3",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Eray Hanoglu <e.hanoglu@panates.com>",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"ts-gems": "^2.5.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@sqb/builder": "^4.10.
|
|
40
|
-
"@sqb/connect": "^4.10.
|
|
41
|
-
"@sqb/postgres": "^4.10.
|
|
39
|
+
"@sqb/builder": "^4.10.3",
|
|
40
|
+
"@sqb/connect": "^4.10.3",
|
|
41
|
+
"@sqb/postgres": "^4.10.3"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=16.0",
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Connection } from 'postgresql-client';
|
|
2
|
+
import { StrictOmit } from 'ts-gems';
|
|
2
3
|
import type { DbMigratorOptions } from '../db-migrator.js';
|
|
3
4
|
import { MigrationAdapter } from '../migration-adapter.js';
|
|
4
|
-
import { MigrationTask } from '../migration-package.js';
|
|
5
|
+
import { Migration, MigrationPackage, MigrationTask } from '../migration-package.js';
|
|
5
6
|
import { MigrationStatus } from '../types.js';
|
|
6
7
|
export declare class PgMigrationAdapter extends MigrationAdapter {
|
|
7
8
|
protected _connection: Connection;
|
|
8
9
|
protected _infoSchema: string;
|
|
9
|
-
protected
|
|
10
|
+
protected _migrationPackage: MigrationPackage;
|
|
10
11
|
protected _version: number;
|
|
11
12
|
protected _status: MigrationStatus;
|
|
12
13
|
protected defaultVariables: {
|
|
@@ -22,7 +23,9 @@ export declare class PgMigrationAdapter extends MigrationAdapter {
|
|
|
22
23
|
get infoSchema(): string;
|
|
23
24
|
get summaryTableFull(): string;
|
|
24
25
|
get eventTableFull(): string;
|
|
25
|
-
static create(options: DbMigratorOptions
|
|
26
|
+
static create(options: StrictOmit<DbMigratorOptions, 'migrationPackage'> & {
|
|
27
|
+
migrationPackage: MigrationPackage;
|
|
28
|
+
}): Promise<PgMigrationAdapter>;
|
|
26
29
|
close(): Promise<void>;
|
|
27
30
|
refresh(): Promise<void>;
|
|
28
31
|
update(info: {
|
|
@@ -30,7 +33,7 @@ export declare class PgMigrationAdapter extends MigrationAdapter {
|
|
|
30
33
|
version?: number;
|
|
31
34
|
}): Promise<void>;
|
|
32
35
|
writeEvent(event: MigrationAdapter.Event): Promise<void>;
|
|
33
|
-
executeTask(task: MigrationTask, variables: Record<string, any>): Promise<void>;
|
|
36
|
+
executeTask(migrationPackage: MigrationPackage, migration: Migration, task: MigrationTask, variables: Record<string, any>): Promise<void>;
|
|
34
37
|
backupDatabase(): Promise<void>;
|
|
35
38
|
lockSchema(): Promise<void>;
|
|
36
39
|
restoreDatabase(): Promise<void>;
|
package/types/db-migrator.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AsyncEventEmitter } from 'strict-typed-events';
|
|
2
2
|
import { ClientConfiguration } from '@sqb/connect';
|
|
3
3
|
import { MigrationAdapter } from './migration-adapter.js';
|
|
4
|
-
import { MigrationPackage,
|
|
4
|
+
import { MigrationPackage, MigrationPackageConfig } from './migration-package.js';
|
|
5
5
|
export interface DbMigratorOptions {
|
|
6
6
|
connection: ClientConfiguration;
|
|
7
|
-
migrationPackage: MigrationPackage |
|
|
7
|
+
migrationPackage: MigrationPackage | MigrationPackageConfig;
|
|
8
8
|
infoSchema?: string;
|
|
9
9
|
scriptVariables?: Record<string, string>;
|
|
10
10
|
targetVersion?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MigrationTask } from './migration-package.js';
|
|
2
|
-
import { MigrationStatus } from './types.js';
|
|
1
|
+
import type { Migration, MigrationPackage, MigrationTask } from './migration-package.js';
|
|
2
|
+
import type { MigrationStatus } from './types.js';
|
|
3
3
|
export declare abstract class MigrationAdapter {
|
|
4
4
|
abstract readonly packageName: string;
|
|
5
5
|
abstract readonly status: MigrationStatus;
|
|
@@ -11,7 +11,7 @@ export declare abstract class MigrationAdapter {
|
|
|
11
11
|
version?: number;
|
|
12
12
|
}): Promise<void>;
|
|
13
13
|
abstract writeEvent(event: MigrationAdapter.Event): Promise<void>;
|
|
14
|
-
abstract executeTask(task: MigrationTask, variables: Record<string, any>): Promise<void>;
|
|
14
|
+
abstract executeTask(migrationPackage: MigrationPackage, migration: Migration, task: MigrationTask, variables: Record<string, any>): Promise<void>;
|
|
15
15
|
abstract lockSchema(): Promise<void>;
|
|
16
16
|
abstract unlockSchema(): Promise<void>;
|
|
17
17
|
abstract backupDatabase(): Promise<void>;
|
|
@@ -27,6 +27,8 @@ export declare namespace MigrationAdapter {
|
|
|
27
27
|
event: EventKind;
|
|
28
28
|
version: number;
|
|
29
29
|
message: string;
|
|
30
|
+
title?: string;
|
|
31
|
+
filename?: string;
|
|
30
32
|
details?: string;
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -1,44 +1,42 @@
|
|
|
1
|
-
import { StrictOmit } from 'ts-gems';
|
|
1
|
+
import { PartialSome, StrictOmit } from 'ts-gems';
|
|
2
2
|
import type { MigrationAdapter } from './migration-adapter.js';
|
|
3
3
|
export interface MigrationPackage {
|
|
4
4
|
name: string;
|
|
5
5
|
description?: string;
|
|
6
6
|
migrations: Migration[];
|
|
7
|
+
baseDir: string;
|
|
7
8
|
informationTableName?: string;
|
|
8
9
|
}
|
|
9
10
|
export interface Migration {
|
|
10
11
|
version: number;
|
|
11
12
|
tasks: MigrationTask[];
|
|
12
|
-
|
|
13
|
+
baseDir: string;
|
|
13
14
|
backup?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export type MigrationTask = SqlScriptMigrationTask | CustomMigrationTask | InsertDataMigrationTask;
|
|
16
17
|
export interface BaseMigrationTask {
|
|
17
|
-
title
|
|
18
|
+
title?: string;
|
|
18
19
|
filename?: string;
|
|
19
20
|
}
|
|
20
21
|
export interface SqlScriptMigrationTask extends BaseMigrationTask {
|
|
21
|
-
|
|
22
|
-
script: string;
|
|
22
|
+
script: string | Function;
|
|
23
23
|
}
|
|
24
24
|
export interface InsertDataMigrationTask extends BaseMigrationTask {
|
|
25
|
-
title: string;
|
|
26
25
|
tableName: string;
|
|
27
26
|
rows: Record<string, any>[];
|
|
28
27
|
}
|
|
29
28
|
export interface CustomMigrationTask extends BaseMigrationTask {
|
|
30
|
-
title: string;
|
|
31
29
|
fn: (connection: any, adapter: MigrationAdapter) => void | Promise<void>;
|
|
32
30
|
}
|
|
33
31
|
export declare function isSqlScriptMigrationTask(x: any): x is SqlScriptMigrationTask;
|
|
34
32
|
export declare function isInsertDataMigrationTask(x: any): x is InsertDataMigrationTask;
|
|
35
33
|
export declare function isCustomMigrationTask(x: any): x is CustomMigrationTask;
|
|
36
|
-
export interface
|
|
37
|
-
migrations: (string |
|
|
34
|
+
export interface MigrationPackageConfig extends PartialSome<StrictOmit<MigrationPackage, 'migrations'>, 'baseDir'> {
|
|
35
|
+
migrations: (string | MigrationConfig | (() => MigrationConfig) | (() => Promise<MigrationConfig>))[];
|
|
38
36
|
}
|
|
39
|
-
export interface
|
|
37
|
+
export interface MigrationConfig extends StrictOmit<Migration, 'tasks' | 'baseDir'> {
|
|
40
38
|
tasks: (string | MigrationTask | (() => MigrationTask) | (() => Promise<MigrationTask>))[];
|
|
41
39
|
}
|
|
42
40
|
export declare namespace MigrationPackage {
|
|
43
|
-
function load(asyncConfig:
|
|
41
|
+
function load(asyncConfig: MigrationPackageConfig): Promise<MigrationPackage>;
|
|
44
42
|
}
|