@sqb/migrator 4.9.0 → 4.10.0
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 +198 -0
- package/cjs/db-migrator.js +77 -184
- package/cjs/index.js +2 -0
- package/cjs/migration-adapter.js +14 -0
- package/cjs/migration-package.js +138 -0
- package/cjs/types.js +6 -0
- package/cjs/utils/get-calling-filename.js +23 -0
- package/esm/adapters/pg-migration-adapter.js +194 -0
- package/esm/db-migrator.js +77 -183
- package/esm/index.js +2 -0
- package/esm/migration-adapter.js +10 -0
- package/esm/migration-package.js +131 -0
- package/esm/types.js +5 -1
- package/esm/utils/get-calling-filename.js +19 -0
- package/package.json +11 -13
- package/cjs/load-task-files.js +0 -39
- package/esm/db-migrator.d.ts +0 -29
- package/esm/index.d.ts +0 -2
- package/esm/load-task-files.d.ts +0 -2
- package/esm/load-task-files.js +0 -34
- package/esm/types.d.ts +0 -36
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgMigrationAdapter = void 0;
|
|
4
|
+
const postgresql_client_1 = require("postgresql-client");
|
|
5
|
+
const postgres_1 = require("@sqb/postgres");
|
|
6
|
+
const migration_adapter_js_1 = require("../migration-adapter.js");
|
|
7
|
+
const migration_package_js_1 = require("../migration-package.js");
|
|
8
|
+
const types_js_1 = require("../types.js");
|
|
9
|
+
const pgAdapter = new postgres_1.PgAdapter();
|
|
10
|
+
class PgMigrationAdapter extends migration_adapter_js_1.MigrationAdapter {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this._infoSchema = 'public';
|
|
14
|
+
this._packageName = '';
|
|
15
|
+
this._version = 0;
|
|
16
|
+
this._status = types_js_1.MigrationStatus.idle;
|
|
17
|
+
this.defaultVariables = {
|
|
18
|
+
tablespace: 'pg_default',
|
|
19
|
+
schema: 'public',
|
|
20
|
+
owner: 'postgres'
|
|
21
|
+
};
|
|
22
|
+
this.summaryTable = 'migration_summary';
|
|
23
|
+
this.eventTable = 'migration_events';
|
|
24
|
+
}
|
|
25
|
+
get packageName() {
|
|
26
|
+
return this._packageName;
|
|
27
|
+
}
|
|
28
|
+
get version() {
|
|
29
|
+
return this._version;
|
|
30
|
+
}
|
|
31
|
+
get status() {
|
|
32
|
+
return this._status;
|
|
33
|
+
}
|
|
34
|
+
get infoSchema() {
|
|
35
|
+
return this._infoSchema;
|
|
36
|
+
}
|
|
37
|
+
get summaryTableFull() {
|
|
38
|
+
return this.infoSchema + '.' + this.summaryTable;
|
|
39
|
+
}
|
|
40
|
+
get eventTableFull() {
|
|
41
|
+
return this.infoSchema + '.' + this.eventTable;
|
|
42
|
+
}
|
|
43
|
+
static async create(options) {
|
|
44
|
+
// Create connection
|
|
45
|
+
const connection = (await pgAdapter.connect(options.connection)).intlcon;
|
|
46
|
+
try {
|
|
47
|
+
const adapter = new PgMigrationAdapter();
|
|
48
|
+
adapter._connection = connection;
|
|
49
|
+
adapter._infoSchema = options.infoSchema || '__migration';
|
|
50
|
+
adapter.defaultVariables.schema = options.connection.schema || '';
|
|
51
|
+
if (!adapter.defaultVariables.schema) {
|
|
52
|
+
const r = await connection.query('SELECT CURRENT_SCHEMA ', { objectRows: true });
|
|
53
|
+
adapter.defaultVariables.schema = (r.rows?.[0]?.current_schema) || 'public';
|
|
54
|
+
}
|
|
55
|
+
// Check if migration schema
|
|
56
|
+
await connection.query(`CREATE SCHEMA IF NOT EXISTS ${adapter.infoSchema} AUTHORIZATION postgres;`);
|
|
57
|
+
// Create summary table if not exists
|
|
58
|
+
await connection.execute(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS ${adapter.summaryTableFull}
|
|
60
|
+
(
|
|
61
|
+
package_name varchar not null,
|
|
62
|
+
status varchar(16) not null,
|
|
63
|
+
current_version integer not null default 0,
|
|
64
|
+
created_at timestamp without time zone not null default current_timestamp,
|
|
65
|
+
updated_at timestamp without time zone,
|
|
66
|
+
CONSTRAINT pk_${adapter.summaryTable} PRIMARY KEY (package_name)
|
|
67
|
+
)`);
|
|
68
|
+
// Create events table if not exists
|
|
69
|
+
await connection.execute(`
|
|
70
|
+
CREATE TABLE IF NOT EXISTS ${adapter.eventTableFull}
|
|
71
|
+
(
|
|
72
|
+
id serial not null,
|
|
73
|
+
package_name varchar not null,
|
|
74
|
+
version integer not null default 0,
|
|
75
|
+
event varchar(16) not null,
|
|
76
|
+
event_time timestamp without time zone not null,
|
|
77
|
+
message text not null,
|
|
78
|
+
details text,
|
|
79
|
+
CONSTRAINT pk_${adapter.eventTable} PRIMARY KEY (id)
|
|
80
|
+
)`);
|
|
81
|
+
// Insert summary record if not exists
|
|
82
|
+
const r = await connection.query(`SELECT status FROM ${adapter.summaryTableFull} WHERE package_name = $1`, { params: [adapter.packageName], objectRows: true });
|
|
83
|
+
if (!(r && r.rows?.length)) {
|
|
84
|
+
await connection.query(`insert into ${adapter.summaryTableFull} ` +
|
|
85
|
+
'(package_name, status) values ($1, $2)', { params: [adapter.packageName, types_js_1.MigrationStatus.idle] });
|
|
86
|
+
}
|
|
87
|
+
await adapter.refresh();
|
|
88
|
+
return adapter;
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
await connection.close(0);
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async close() {
|
|
96
|
+
await this._connection.close();
|
|
97
|
+
}
|
|
98
|
+
async refresh() {
|
|
99
|
+
const r = await this._connection.query(`SELECT * FROM ${this.summaryTableFull} WHERE package_name = $1`, { params: [this.packageName], objectRows: true });
|
|
100
|
+
const row = r.rows && r.rows[0];
|
|
101
|
+
if (!row)
|
|
102
|
+
throw new Error('Summary record did not created');
|
|
103
|
+
this._version = row.current_version;
|
|
104
|
+
this._status = row.status;
|
|
105
|
+
}
|
|
106
|
+
async update(info) {
|
|
107
|
+
let sql = '';
|
|
108
|
+
const params = [];
|
|
109
|
+
if (info.status && info.status !== this.status) {
|
|
110
|
+
params.push(info.status);
|
|
111
|
+
sql += ', status = $' + (params.length);
|
|
112
|
+
}
|
|
113
|
+
if (info.version && info.version !== this.version) {
|
|
114
|
+
params.push(info.version);
|
|
115
|
+
sql += ', current_version = $' + (params.length);
|
|
116
|
+
}
|
|
117
|
+
if (sql) {
|
|
118
|
+
sql = `update ${this.summaryTableFull} set updated_at = current_timestamp` + sql;
|
|
119
|
+
await this._connection.query(sql, { params });
|
|
120
|
+
if (info.status)
|
|
121
|
+
this._status = info.status;
|
|
122
|
+
if (info.version)
|
|
123
|
+
this._version = info.version;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async writeEvent(event) {
|
|
127
|
+
const sql = `insert into ${this.eventTableFull} ` +
|
|
128
|
+
'(package_name, version, event, event_time, message, details) ' +
|
|
129
|
+
'values ($1, $2, $3, CURRENT_TIMESTAMP, $4, $5)';
|
|
130
|
+
await this._connection.query(sql, {
|
|
131
|
+
params: [
|
|
132
|
+
this.packageName, event.version, event.event,
|
|
133
|
+
event.message, event.details
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async executeTask(task, variables) {
|
|
138
|
+
variables = {
|
|
139
|
+
...this.defaultVariables,
|
|
140
|
+
...variables
|
|
141
|
+
};
|
|
142
|
+
if ((0, migration_package_js_1.isSqlScriptMigrationTask)(task)) {
|
|
143
|
+
try {
|
|
144
|
+
const script = task.script
|
|
145
|
+
.replace(/(\${(\w+)})/g, (s, ...args) => variables[args[1]] || s);
|
|
146
|
+
await this._connection.execute(script);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
let msg = `Error in task "${task.title}"`;
|
|
150
|
+
if (task.filename)
|
|
151
|
+
msg += '\n at ' + task.filename;
|
|
152
|
+
if (e.lineNr) {
|
|
153
|
+
if (!task.filename)
|
|
154
|
+
e.message += '\n at';
|
|
155
|
+
msg += ` (${e.lineNr},${e.colNr}):\n` + e.line;
|
|
156
|
+
if (e.colNr)
|
|
157
|
+
msg += '\n' + ' '.repeat(e.colNr - 1) + '^';
|
|
158
|
+
}
|
|
159
|
+
e.message = msg + '\n\n' + e.message;
|
|
160
|
+
throw e;
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if ((0, migration_package_js_1.isCustomMigrationTask)(task)) {
|
|
165
|
+
await task.fn(this._connection, this);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if ((0, migration_package_js_1.isInsertDataMigrationTask)(task)) {
|
|
169
|
+
const script = task.rows
|
|
170
|
+
.map(row => this.rowToSql(variables.schema + '.' + task.tableName, row))
|
|
171
|
+
.join('\n');
|
|
172
|
+
await this._connection.execute(script);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
backupDatabase() {
|
|
176
|
+
return Promise.resolve(undefined);
|
|
177
|
+
}
|
|
178
|
+
lockSchema() {
|
|
179
|
+
return Promise.resolve(undefined);
|
|
180
|
+
}
|
|
181
|
+
restoreDatabase() {
|
|
182
|
+
return Promise.resolve(undefined);
|
|
183
|
+
}
|
|
184
|
+
unlockSchema() {
|
|
185
|
+
return Promise.resolve(undefined);
|
|
186
|
+
}
|
|
187
|
+
rowToSql(tableName, row) {
|
|
188
|
+
let sql = '';
|
|
189
|
+
const keys = Object.keys(row);
|
|
190
|
+
sql += `insert into ${tableName} (${keys}) values (`;
|
|
191
|
+
for (let i = 0; i < keys.length; i++) {
|
|
192
|
+
sql += (i ? ', ' : '') + (0, postgresql_client_1.stringifyValueForSQL)(row[keys[i]]);
|
|
193
|
+
}
|
|
194
|
+
sql += ');\n';
|
|
195
|
+
return sql;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
exports.PgMigrationAdapter = PgMigrationAdapter;
|
package/cjs/db-migrator.js
CHANGED
|
@@ -1,215 +1,108 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DbMigrator = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
class DbMigrator {
|
|
4
|
+
const strict_typed_events_1 = require("strict-typed-events");
|
|
5
|
+
const pg_migration_adapter_js_1 = require("./adapters/pg-migration-adapter.js");
|
|
6
|
+
const migration_adapter_js_1 = require("./migration-adapter.js");
|
|
7
|
+
const migration_package_js_1 = require("./migration-package.js");
|
|
8
|
+
const types_js_1 = require("./types.js");
|
|
9
|
+
class DbMigrator extends strict_typed_events_1.AsyncEventEmitter {
|
|
11
10
|
async execute(options) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
11
|
+
if (!options.connection.dialect)
|
|
12
|
+
throw new TypeError(`You must provide connection.dialect`);
|
|
13
|
+
const migrationPackage = await migration_package_js_1.MigrationPackage.load(options.migrationPackage);
|
|
14
|
+
let minVersion = migrationPackage.migrations
|
|
15
|
+
.reduce((a, m) => Math.min(a, m.version), Number.MAX_SAFE_INTEGER);
|
|
16
|
+
if (minVersion === Number.MAX_SAFE_INTEGER)
|
|
17
|
+
minVersion = 0;
|
|
18
|
+
const maxVersion = migrationPackage.migrations
|
|
19
|
+
.reduce((a, m) => Math.max(a, m.version), 0);
|
|
20
|
+
const targetVersion = Math.min(options?.targetVersion || Number.MAX_SAFE_INTEGER, maxVersion);
|
|
21
|
+
if (targetVersion && targetVersion < minVersion) { // noinspection ExceptionCaughtLocallyJS
|
|
16
22
|
throw new Error(`Version mismatch. Target schema version (${targetVersion}) is lower than ` +
|
|
17
|
-
`migration package min version (${
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
let
|
|
23
|
+
`migration package min version (${minVersion})`);
|
|
24
|
+
}
|
|
25
|
+
let migrationAdapter;
|
|
26
|
+
switch (options.connection.dialect) {
|
|
27
|
+
case 'postgres': {
|
|
28
|
+
migrationAdapter = await pg_migration_adapter_js_1.PgMigrationAdapter.create(options);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
default:
|
|
32
|
+
throw new TypeError(`Migration adapter for "${options.connection.dialect}" dialect is not implemented yet`);
|
|
33
|
+
}
|
|
34
|
+
let needBackup = false;
|
|
29
35
|
try {
|
|
30
|
-
|
|
36
|
+
if (migrationAdapter.version && migrationAdapter.version < minVersion - 1) { // noinspection ExceptionCaughtLocallyJS
|
|
37
|
+
throw new Error(`This package can migrate starting from ${minVersion - 1} but current version is ${migrationAdapter.version}`);
|
|
38
|
+
}
|
|
39
|
+
const { migrations } = migrationPackage;
|
|
40
|
+
// calculate total scripts;
|
|
41
|
+
const total = migrations
|
|
42
|
+
.reduce((i, x) => i + x.tasks.length, 0);
|
|
43
|
+
needBackup = !!migrations.find(x => !!x.backup);
|
|
44
|
+
await this.emitAsync('start');
|
|
45
|
+
let task;
|
|
46
|
+
await migrationAdapter.lockSchema();
|
|
31
47
|
if (needBackup) {
|
|
32
|
-
|
|
33
|
-
await
|
|
48
|
+
await this.emitAsync('backup');
|
|
49
|
+
await migrationAdapter.backupDatabase();
|
|
34
50
|
}
|
|
35
|
-
await connection.execute(`update ${schema}.${schemaInfo.infTable} set status = 'migration'`);
|
|
36
51
|
// Execute migration tasks
|
|
37
52
|
for (const migration of migrations) {
|
|
38
|
-
if (migration.version > targetVersion ||
|
|
53
|
+
if (migration.version > targetVersion || migrationAdapter.version >= migration.version)
|
|
39
54
|
continue;
|
|
40
55
|
for (let index = 0; index < migration.tasks.length; index++) {
|
|
41
56
|
task = migration.tasks[index];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
await this.emitAsync('task-start', { task: task.title, total, index });
|
|
58
|
+
await migrationAdapter.update({ status: types_js_1.MigrationStatus.busy });
|
|
59
|
+
await migrationAdapter.writeEvent({
|
|
60
|
+
event: migration_adapter_js_1.MigrationAdapter.EventKind.started,
|
|
61
|
+
version: migration.version,
|
|
62
|
+
message: `Task "${task.title}" started`
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
await migrationAdapter.executeTask(task, {
|
|
66
|
+
schema: options.connection.schema,
|
|
67
|
+
...options.scriptVariables,
|
|
68
|
+
});
|
|
69
|
+
await migrationAdapter.writeEvent({
|
|
70
|
+
event: migration_adapter_js_1.MigrationAdapter.EventKind.success,
|
|
71
|
+
version: migration.version,
|
|
72
|
+
message: `Task "${task.title}" completed`
|
|
57
73
|
});
|
|
58
|
-
try {
|
|
59
|
-
await connection.execute(script);
|
|
60
|
-
}
|
|
61
|
-
catch (e) {
|
|
62
|
-
e.message += `\n${task.title}`;
|
|
63
|
-
if (e.lineNr)
|
|
64
|
-
e.message += ` (${e.lineNr},${e.colNr}):\n` + e.line;
|
|
65
|
-
if (e.colNr)
|
|
66
|
-
e.message += '\n' + ' '.repeat(e.colNr - 1) + '^';
|
|
67
|
-
// noinspection ExceptionCaughtLocallyJS
|
|
68
|
-
throw e;
|
|
69
|
-
}
|
|
70
74
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
catch (e) {
|
|
76
|
+
await migrationAdapter.writeEvent({
|
|
77
|
+
event: migration_adapter_js_1.MigrationAdapter.EventKind.error,
|
|
78
|
+
version: migration.version,
|
|
79
|
+
message: String(e)
|
|
80
|
+
});
|
|
81
|
+
// noinspection ExceptionCaughtLocallyJS
|
|
82
|
+
throw e;
|
|
74
83
|
}
|
|
75
|
-
|
|
84
|
+
await this.emitAsync('task-finish', { task: task.title, total, index });
|
|
76
85
|
}
|
|
86
|
+
await migrationAdapter.update({ version: migration.version });
|
|
77
87
|
}
|
|
78
|
-
await connection.query(`update ${schema}.${schemaInfo.infTable} set status = $1, schema_version = $2, ` +
|
|
79
|
-
'updated_at = current_timestamp', { params: ['ready', targetVersion] });
|
|
80
88
|
}
|
|
81
89
|
catch (e) {
|
|
82
90
|
if (needBackup) {
|
|
83
|
-
|
|
84
|
-
await
|
|
91
|
+
await this.emitAsync('restore');
|
|
92
|
+
await migrationAdapter.restoreDatabase();
|
|
85
93
|
}
|
|
86
94
|
throw e;
|
|
87
95
|
}
|
|
88
96
|
finally {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
emit('finish');
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
static async loadMigrationPackage(pkg) {
|
|
95
|
-
const migarr = typeof pkg.migrations === 'function' ? await pkg.migrations() : pkg.migrations;
|
|
96
|
-
const migrations = [];
|
|
97
|
-
for (const x of migarr) {
|
|
98
|
-
if (typeof x === 'string')
|
|
99
|
-
await locateMigrations(migrations, x);
|
|
100
|
-
else
|
|
101
|
-
migrations.push(x);
|
|
102
|
-
}
|
|
103
|
-
let minVersion = 0;
|
|
104
|
-
let maxVersion = 0;
|
|
105
|
-
migrations.sort((a, b) => a.version - b.version);
|
|
106
|
-
for (const m of migrations) {
|
|
107
|
-
minVersion = !minVersion ? m.version : Math.min(minVersion, m.version);
|
|
108
|
-
maxVersion = Math.max(minVersion, m.version);
|
|
109
|
-
for (const t of m.tasks) {
|
|
110
|
-
if (typeof t === 'string') {
|
|
111
|
-
m.tasks = (0, load_task_files_js_1.loadTaskFiles)(t);
|
|
112
|
-
}
|
|
97
|
+
try {
|
|
98
|
+
await migrationAdapter.unlockSchema();
|
|
113
99
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
minVersion,
|
|
117
|
-
maxVersion,
|
|
118
|
-
migrations
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
async _lockSchema() {
|
|
122
|
-
// todo
|
|
123
|
-
}
|
|
124
|
-
async _unlockSchema() {
|
|
125
|
-
// todo
|
|
126
|
-
}
|
|
127
|
-
async _backup() {
|
|
128
|
-
// todo
|
|
129
|
-
}
|
|
130
|
-
async _restore() {
|
|
131
|
-
// todo
|
|
132
|
-
}
|
|
133
|
-
async _getSchemaInfo(options) {
|
|
134
|
-
const { connection, schema } = options;
|
|
135
|
-
let owner = options.owner || 'postgres';
|
|
136
|
-
let tablespace = options.tablespace || 'pg_default';
|
|
137
|
-
let version = 0;
|
|
138
|
-
let status = '';
|
|
139
|
-
let r = await connection.query('select schema_owner FROM information_schema.schemata WHERE schema_name = $1', { params: [schema], objectRows: true });
|
|
140
|
-
if (r.rows && r.rows[0]) {
|
|
141
|
-
owner = r.rows[0].schema_owner;
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
await connection.execute(`CREATE SCHEMA ${schema} AUTHORIZATION ${owner};`);
|
|
145
|
-
}
|
|
146
|
-
// Determine tablespace of schema
|
|
147
|
-
const infTable = options.migrationPackage.informationTableName || '__schema_info';
|
|
148
|
-
r = await connection.query('SELECT tablespace FROM pg_tables WHERE schemaname = $1 AND tablename = $2', { params: [schema, infTable], objectRows: true });
|
|
149
|
-
if (r.rows && r.rows[0]) {
|
|
150
|
-
tablespace = r.rows[0].tablespace || tablespace;
|
|
151
|
-
r = await connection.query(`SELECT schema_version, status FROM ${schema}.${infTable}`, { objectRows: true });
|
|
152
|
-
if (r.rows && r.rows[0]) {
|
|
153
|
-
version = r.rows[0].schema_version;
|
|
154
|
-
status = r.rows[0].status;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
// Create if not exists
|
|
159
|
-
await connection.execute(`
|
|
160
|
-
CREATE TABLE ${schema}.${infTable}
|
|
161
|
-
(
|
|
162
|
-
status varchar(16) not null,
|
|
163
|
-
schema_version integer not null default 0,
|
|
164
|
-
created_at timestamp without time zone not null default current_timestamp,
|
|
165
|
-
updated_at timestamp without time zone
|
|
166
|
-
) TABLESPACE ${tablespace};
|
|
167
|
-
insert into ${schema}.${infTable} (status) values ('init');
|
|
168
|
-
`);
|
|
169
|
-
status = 'init';
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
infTable,
|
|
173
|
-
tablespace,
|
|
174
|
-
owner,
|
|
175
|
-
version,
|
|
176
|
-
status
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
static objectDataToSql(task) {
|
|
180
|
-
let sql = '';
|
|
181
|
-
for (const rows of task.rows) {
|
|
182
|
-
const keys = Object.keys(rows);
|
|
183
|
-
sql += `insert into ${task.schema}.${task.tableName} (${keys}) values (`;
|
|
184
|
-
for (let i = 0; i < keys.length; i++) {
|
|
185
|
-
sql += (i ? ', ' : '') + (0, postgresql_client_1.stringifyValueForSQL)(rows[keys[i]]);
|
|
100
|
+
finally {
|
|
101
|
+
await migrationAdapter.close();
|
|
186
102
|
}
|
|
187
|
-
sql += ');\n';
|
|
188
103
|
}
|
|
189
|
-
|
|
104
|
+
await this.emitAsync('finish');
|
|
105
|
+
return true;
|
|
190
106
|
}
|
|
191
107
|
}
|
|
192
108
|
exports.DbMigrator = DbMigrator;
|
|
193
|
-
async function locateMigrations(trg, dir) {
|
|
194
|
-
for (const f of ['migration.ts', 'migration.js', 'migration.json']) {
|
|
195
|
-
const filename = node_path_1.default.join(dir, f);
|
|
196
|
-
if (node_fs_1.default.existsSync(filename)) {
|
|
197
|
-
const x = await Promise.resolve(`${filename}`).then(s => tslib_1.__importStar(require(s)));
|
|
198
|
-
const fileDir = node_path_1.default.dirname(filename);
|
|
199
|
-
const obj = x.default || x;
|
|
200
|
-
if (obj.version && obj.tasks) {
|
|
201
|
-
const migration = { ...obj };
|
|
202
|
-
migration.tasks = obj.tasks.map(k => (typeof k === 'string' ? node_path_1.default.resolve(fileDir, k) : k));
|
|
203
|
-
trg.push(migration);
|
|
204
|
-
}
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
const files = node_fs_1.default.readdirSync(dir);
|
|
209
|
-
for (const f of files) {
|
|
210
|
-
const dirname = node_path_1.default.join(dir, f);
|
|
211
|
-
if (node_fs_1.default.statSync(dirname).isDirectory()) {
|
|
212
|
-
await locateMigrations(trg, dirname);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
package/cjs/index.js
CHANGED
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
tslib_1.__exportStar(require("./db-migrator.js"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./migration-package.js"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./migration-adapter.js"), exports);
|
|
5
7
|
tslib_1.__exportStar(require("./types.js"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MigrationAdapter = void 0;
|
|
4
|
+
class MigrationAdapter {
|
|
5
|
+
}
|
|
6
|
+
exports.MigrationAdapter = MigrationAdapter;
|
|
7
|
+
(function (MigrationAdapter) {
|
|
8
|
+
let EventKind;
|
|
9
|
+
(function (EventKind) {
|
|
10
|
+
EventKind["started"] = "started";
|
|
11
|
+
EventKind["success"] = "success";
|
|
12
|
+
EventKind["error"] = "error";
|
|
13
|
+
})(EventKind = MigrationAdapter.EventKind || (MigrationAdapter.EventKind = {}));
|
|
14
|
+
})(MigrationAdapter || (exports.MigrationAdapter = MigrationAdapter = {}));
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MigrationPackage = exports.isCustomMigrationTask = exports.isInsertDataMigrationTask = exports.isSqlScriptMigrationTask = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fast_glob_1 = tslib_1.__importDefault(require("fast-glob"));
|
|
6
|
+
const promises_1 = tslib_1.__importDefault(require("fs/promises"));
|
|
7
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
8
|
+
const get_calling_filename_js_1 = require("./utils/get-calling-filename.js");
|
|
9
|
+
function isSqlScriptMigrationTask(x) {
|
|
10
|
+
return typeof x === 'object' && typeof x.script === 'string';
|
|
11
|
+
}
|
|
12
|
+
exports.isSqlScriptMigrationTask = isSqlScriptMigrationTask;
|
|
13
|
+
function isInsertDataMigrationTask(x) {
|
|
14
|
+
return typeof x === 'object' &&
|
|
15
|
+
typeof x.tableName === 'string' &&
|
|
16
|
+
Array.isArray(x.rows);
|
|
17
|
+
}
|
|
18
|
+
exports.isInsertDataMigrationTask = isInsertDataMigrationTask;
|
|
19
|
+
function isCustomMigrationTask(x) {
|
|
20
|
+
return typeof x === 'object' &&
|
|
21
|
+
typeof x.fn === 'function';
|
|
22
|
+
}
|
|
23
|
+
exports.isCustomMigrationTask = isCustomMigrationTask;
|
|
24
|
+
var MigrationPackage;
|
|
25
|
+
(function (MigrationPackage) {
|
|
26
|
+
async function load(asyncConfig) {
|
|
27
|
+
const out = {
|
|
28
|
+
...asyncConfig,
|
|
29
|
+
migrations: []
|
|
30
|
+
};
|
|
31
|
+
if (!Array.isArray(asyncConfig.migrations))
|
|
32
|
+
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
|
+
if (asyncConfig.migrations?.length) {
|
|
35
|
+
const srcMigrations = [];
|
|
36
|
+
const trgMigrations = [];
|
|
37
|
+
out.migrations = trgMigrations;
|
|
38
|
+
let x;
|
|
39
|
+
for (x of asyncConfig.migrations) {
|
|
40
|
+
x = typeof x === 'function' ? await x() : x;
|
|
41
|
+
if (typeof x === 'object' && x.tasks)
|
|
42
|
+
srcMigrations.push(x);
|
|
43
|
+
else if (typeof x === 'string') {
|
|
44
|
+
srcMigrations.push(...await loadMigrations(path_1.default.resolve(baseDir, x.replace(/\\/g, '/'))));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
srcMigrations.sort((a, b) => a.version - b.version);
|
|
48
|
+
for (const migration of srcMigrations) {
|
|
49
|
+
const trgMigration = { ...migration, tasks: [] };
|
|
50
|
+
trgMigrations.push(trgMigration);
|
|
51
|
+
const srcTasks = migration.tasks;
|
|
52
|
+
trgMigration.tasks = [];
|
|
53
|
+
for (const t of srcTasks) {
|
|
54
|
+
if (typeof t === 'object') {
|
|
55
|
+
trgMigration.tasks.push(t);
|
|
56
|
+
}
|
|
57
|
+
else if (typeof t === 'string') {
|
|
58
|
+
let pattern = t.replace(/\\/g, '/');
|
|
59
|
+
pattern = path_1.default.resolve(migration.dirname || baseDir, pattern);
|
|
60
|
+
const files = await (0, fast_glob_1.default)(pattern, { absolute: true, onlyFiles: true });
|
|
61
|
+
for (const filename of files) {
|
|
62
|
+
const ext = path_1.default.extname(filename).toLowerCase();
|
|
63
|
+
if (!path_1.default.basename(filename, ext).endsWith('.task'))
|
|
64
|
+
continue;
|
|
65
|
+
if (ext === '.sql') {
|
|
66
|
+
const script = await promises_1.default.readFile(filename, 'utf-8');
|
|
67
|
+
trgMigration.tasks.push({
|
|
68
|
+
title: path_1.default.basename(filename),
|
|
69
|
+
filename,
|
|
70
|
+
script
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (ext === '.json') {
|
|
74
|
+
try {
|
|
75
|
+
const json = JSON.parse(await promises_1.default.readFile(filename, 'utf-8'));
|
|
76
|
+
if (typeof json !== 'object')
|
|
77
|
+
continue;
|
|
78
|
+
if (json.script) {
|
|
79
|
+
json.title = json.title || 'Run sql script';
|
|
80
|
+
json.filename = filename;
|
|
81
|
+
trgMigration.tasks.push(json);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (json.tableName && json.rows) {
|
|
85
|
+
json.title = json.title || 'Migrate data into ' + json.tableName;
|
|
86
|
+
json.filename = filename;
|
|
87
|
+
trgMigration.tasks.push(json);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (typeof json.fn === 'function') {
|
|
91
|
+
json.title = json.title || 'Run custom function';
|
|
92
|
+
json.filename = filename;
|
|
93
|
+
trgMigration.tasks.push(json);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
e.message = `Error in ${filename}\n` + e.message;
|
|
98
|
+
throw e;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
MigrationPackage.load = load;
|
|
109
|
+
async function loadMigrations(pattern) {
|
|
110
|
+
const out = [];
|
|
111
|
+
const files = await (0, fast_glob_1.default)(pattern, { absolute: true, onlyFiles: true });
|
|
112
|
+
for (const filename of files) {
|
|
113
|
+
const ext = path_1.default.extname(filename).toLowerCase();
|
|
114
|
+
if (path_1.default.basename(filename, ext) !== 'migration')
|
|
115
|
+
continue;
|
|
116
|
+
let json;
|
|
117
|
+
if (['.js', '.ts', '.cjs', '.mjs'].includes(ext)) {
|
|
118
|
+
json = await Promise.resolve(`${filename}`).then(s => tslib_1.__importStar(require(s)));
|
|
119
|
+
if (json.__esModule)
|
|
120
|
+
json = json.default;
|
|
121
|
+
}
|
|
122
|
+
else if (ext === '.json') {
|
|
123
|
+
try {
|
|
124
|
+
json = JSON.parse(await promises_1.default.readFile(filename, 'utf-8'));
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
e.message = `Error in ${filename}\n` + e.message;
|
|
128
|
+
throw e;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (json && typeof json === 'object' && json.version && Array.isArray(json.tasks)) {
|
|
132
|
+
json.dirname = path_1.default.dirname(filename);
|
|
133
|
+
out.push(json);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
})(MigrationPackage || (exports.MigrationPackage = MigrationPackage = {}));
|
package/cjs/types.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MigrationStatus = void 0;
|
|
4
|
+
var MigrationStatus;
|
|
5
|
+
(function (MigrationStatus) {
|
|
6
|
+
MigrationStatus["idle"] = "idle";
|
|
7
|
+
MigrationStatus["busy"] = "busy";
|
|
8
|
+
})(MigrationStatus || (exports.MigrationStatus = MigrationStatus = {}));
|