@carno.js/cli 0.3.7 → 0.4.1
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/LICENSE +673 -673
- package/README.md +102 -102
- package/dist/bin.js +4 -4
- package/dist/migrator/migration-plan.d.ts +1 -0
- package/dist/migrator/migration-plan.js +8 -0
- package/dist/migrator/migration-plan.js.map +1 -0
- package/dist/migrator/migrator.js +29 -6
- package/dist/migrator/migrator.js.map +1 -1
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +35 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/migrator/diff-calculator.d.ts +33 -0
- package/dist/src/migrator/diff-calculator.js +547 -0
- package/dist/src/migrator/diff-calculator.js.map +1 -0
- package/dist/src/migrator/migrator.d.ts +116 -0
- package/dist/src/migrator/migrator.js +715 -0
- package/dist/src/migrator/migrator.js.map +1 -0
- package/dist/src/migrator/snapshot-cleaner.d.ts +7 -0
- package/dist/src/migrator/snapshot-cleaner.js +108 -0
- package/dist/src/migrator/snapshot-cleaner.js.map +1 -0
- package/dist/src/migrator/snapshot-manager.d.ts +10 -0
- package/dist/src/migrator/snapshot-manager.js +74 -0
- package/dist/src/migrator/snapshot-manager.js.map +1 -0
- package/dist/src/routes/routes-command.d.ts +7 -0
- package/dist/src/routes/routes-command.js +220 -0
- package/dist/src/routes/routes-command.js.map +1 -0
- package/dist/tests/node-database.d.ts +7 -0
- package/dist/tests/node-database.js +68 -0
- package/dist/tests/node-database.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Migrator = void 0;
|
|
40
|
+
const LoggerService_1 = require("@carno.js/logger/src/LoggerService");
|
|
41
|
+
const orm_1 = require("@carno.js/orm");
|
|
42
|
+
const orm_2 = require("@carno.js/orm");
|
|
43
|
+
const globby_1 = __importDefault(require("globby"));
|
|
44
|
+
const knex = __importStar(require("knex"));
|
|
45
|
+
const diff_calculator_1 = require("./diff-calculator");
|
|
46
|
+
const snapshot_manager_1 = require("./snapshot-manager");
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
class Migrator {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.entities = new orm_2.EntityStorage();
|
|
52
|
+
this.orm = orm_1.Orm.getInstance();
|
|
53
|
+
if (this.orm === undefined) {
|
|
54
|
+
(0, orm_1.setLogger)(new LoggerService_1.LoggerService({ pretty: true }));
|
|
55
|
+
this.orm = new orm_1.Orm();
|
|
56
|
+
}
|
|
57
|
+
this.entities = orm_2.EntityStorage.getInstance();
|
|
58
|
+
if (this.entities === undefined)
|
|
59
|
+
this.entities = new orm_2.EntityStorage();
|
|
60
|
+
}
|
|
61
|
+
async startConnection(basePath = process.cwd()) {
|
|
62
|
+
await this.initConfigFile(basePath);
|
|
63
|
+
await this.initKnex();
|
|
64
|
+
}
|
|
65
|
+
async initConfigFile(basePath) {
|
|
66
|
+
const paths = await (0, globby_1.default)(['carno.config.ts'], {
|
|
67
|
+
absolute: true,
|
|
68
|
+
cwd: basePath,
|
|
69
|
+
});
|
|
70
|
+
if (paths.length === 0) {
|
|
71
|
+
throw new Error('Config file not found');
|
|
72
|
+
}
|
|
73
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
74
|
+
const config = await dynamicImport(paths[0]);
|
|
75
|
+
this.config = config.default;
|
|
76
|
+
if (typeof this.config.entities === 'string') {
|
|
77
|
+
const paths = await (0, globby_1.default)(this.config.entities, {
|
|
78
|
+
absolute: true,
|
|
79
|
+
cwd: basePath,
|
|
80
|
+
});
|
|
81
|
+
for (const path of paths) {
|
|
82
|
+
console.log(`Importing entity from: ${path}`);
|
|
83
|
+
await dynamicImport(path);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await this.initOrm();
|
|
87
|
+
}
|
|
88
|
+
async initOrm() {
|
|
89
|
+
if (this.orm.driverInstance) {
|
|
90
|
+
try {
|
|
91
|
+
await this.orm.driverInstance.executeSql('SELECT 1');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const serv = new orm_1.OrmService(this.orm, this.entities);
|
|
98
|
+
await serv.onInit(this.config);
|
|
99
|
+
}
|
|
100
|
+
initKnex() {
|
|
101
|
+
if (this.knex) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (!this.orm.driverInstance) {
|
|
105
|
+
throw new Error('ORM must be initialized before Knex');
|
|
106
|
+
}
|
|
107
|
+
const clientType = this.orm.driverInstance.dbType === 'postgres' ? 'pg' : 'mysql2';
|
|
108
|
+
this.knex = knex.default({
|
|
109
|
+
client: clientType,
|
|
110
|
+
connection: {
|
|
111
|
+
host: this.config.host,
|
|
112
|
+
user: this.config.username,
|
|
113
|
+
password: this.config.password,
|
|
114
|
+
database: this.config.database,
|
|
115
|
+
uri: this.config.connectionString,
|
|
116
|
+
},
|
|
117
|
+
debug: true,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async run(diff) {
|
|
121
|
+
const sql = [];
|
|
122
|
+
const sortedDiff = this.sortTablesByDependencies(diff);
|
|
123
|
+
for (const tableDiff of sortedDiff) {
|
|
124
|
+
let query;
|
|
125
|
+
const partialIndexSql = this.buildPartialIndexStatements(tableDiff);
|
|
126
|
+
if (tableDiff.newTable) {
|
|
127
|
+
query = await this.createTable(tableDiff);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const enumAlters = tableDiff.colDiffs.filter((diff) => diff.actionType === 'ALTER' &&
|
|
131
|
+
diff.colType === 'enum' &&
|
|
132
|
+
diff.colChanges?.enumItems);
|
|
133
|
+
for (const enumAlter of enumAlters) {
|
|
134
|
+
const dropConstraints = this.dropEnumConstraints(tableDiff.tableName, enumAlter.colName);
|
|
135
|
+
sql.push(...dropConstraints);
|
|
136
|
+
}
|
|
137
|
+
query = this.knex.schema
|
|
138
|
+
.withSchema(tableDiff.schema)
|
|
139
|
+
.table(tableDiff.tableName, (builder) => {
|
|
140
|
+
for (const colDiff of tableDiff.colDiffs) {
|
|
141
|
+
if (colDiff.actionType === 'INDEX') {
|
|
142
|
+
colDiff.indexTables.forEach((indexTable) => {
|
|
143
|
+
if (typeof indexTable.properties === 'undefined') {
|
|
144
|
+
builder.dropIndex([], indexTable.name);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (this.isPartialIndexTable(indexTable)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
builder.index(indexTable.properties, indexTable.name);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (colDiff.uniqueTables) {
|
|
154
|
+
colDiff.uniqueTables.forEach((uniqueTable) => {
|
|
155
|
+
if (typeof uniqueTable.properties === 'undefined') {
|
|
156
|
+
builder.dropUnique([], uniqueTable.name);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
builder.unique(uniqueTable.properties, uniqueTable.name);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (colDiff.actionType === 'DELETE') {
|
|
163
|
+
builder.dropColumn(colDiff.colName);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (colDiff.actionType === 'ALTER' &&
|
|
167
|
+
!colDiff.colType &&
|
|
168
|
+
typeof colDiff.colChanges === 'undefined')
|
|
169
|
+
continue;
|
|
170
|
+
if (typeof colDiff.colChanges?.unique !== 'undefined') {
|
|
171
|
+
if (colDiff.colChanges?.unique) {
|
|
172
|
+
builder.unique([colDiff.colName], `${tableDiff.tableName}_${colDiff.colName}_key`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
builder.dropUnique([colDiff.colName], `${tableDiff.tableName}_${colDiff.colName}_key`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (typeof colDiff.colChanges?.foreignKeys !== 'undefined') {
|
|
179
|
+
if (colDiff.colChanges.foreignKeys.length !== 0) {
|
|
180
|
+
colDiff.colChanges.foreignKeys.forEach((fk) => {
|
|
181
|
+
builder
|
|
182
|
+
.foreign(colDiff.colName, `${tableDiff.tableName}_${colDiff.colName}_fk`)
|
|
183
|
+
.references(`${fk.referencedTableName}.${fk.referencedColumnName}`);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
builder.dropForeign(colDiff.colName, `${tableDiff.tableName}_${colDiff.colName}_fk`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const columnBuilder = this.assignType(builder, colDiff, tableDiff);
|
|
191
|
+
if (!columnBuilder)
|
|
192
|
+
continue;
|
|
193
|
+
if (colDiff.actionType === 'ALTER') {
|
|
194
|
+
columnBuilder.alter({ alterNullable: false });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
.toSQL();
|
|
200
|
+
}
|
|
201
|
+
sql.push(...query.flatMap((q) => q.sql.concat(';')));
|
|
202
|
+
sql.push(...partialIndexSql);
|
|
203
|
+
}
|
|
204
|
+
return sql;
|
|
205
|
+
}
|
|
206
|
+
async createTable(tableDiff) {
|
|
207
|
+
return await this.knex.schema
|
|
208
|
+
.withSchema(tableDiff.schema)
|
|
209
|
+
.createTable(tableDiff.tableName, (builder) => {
|
|
210
|
+
for (const diff of tableDiff.colDiffs) {
|
|
211
|
+
if (diff.actionType === 'INDEX') {
|
|
212
|
+
diff.indexTables.forEach((indexTable) => {
|
|
213
|
+
if (typeof indexTable.properties === 'undefined') {
|
|
214
|
+
builder.dropIndex([], indexTable.name);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (this.isPartialIndexTable(indexTable)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (indexTable.name.includes('pkey')) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
builder.index(indexTable.properties, indexTable.name);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (diff.uniqueTables) {
|
|
227
|
+
diff.uniqueTables.forEach((uniqueTable) => {
|
|
228
|
+
if (typeof uniqueTable.properties === 'undefined') {
|
|
229
|
+
builder.dropUnique([], uniqueTable.name);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
builder.unique(uniqueTable.properties, uniqueTable.name);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (typeof diff.colChanges?.foreignKeys !== 'undefined') {
|
|
236
|
+
if (diff.colChanges.foreignKeys.length !== 0) {
|
|
237
|
+
diff.colChanges.foreignKeys.forEach((fk) => {
|
|
238
|
+
builder
|
|
239
|
+
.foreign(diff.colName, `${tableDiff.tableName}_${diff.colName}_fk`)
|
|
240
|
+
.references(`${fk.referencedTableName}.${fk.referencedColumnName}`);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
builder.dropForeign(diff.colName, `${tableDiff.tableName}_${diff.colName}_fk`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.assignType(builder, diff, tableDiff);
|
|
248
|
+
if (typeof diff.colChanges?.unique !== 'undefined') {
|
|
249
|
+
if (diff.colChanges?.unique) {
|
|
250
|
+
builder.unique([diff.colName], `${tableDiff.tableName}_${diff.colName}_key`);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
builder.dropUnique([diff.colName], `${tableDiff.tableName}_${diff.colName}_key`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
.toSQL();
|
|
259
|
+
}
|
|
260
|
+
buildPartialIndexStatements(tableDiff) {
|
|
261
|
+
const tables = this.collectPartialIndexTables(tableDiff.colDiffs);
|
|
262
|
+
const statements = tables.map((indexTable) => this.buildPartialIndexStatement(tableDiff, indexTable));
|
|
263
|
+
return statements;
|
|
264
|
+
}
|
|
265
|
+
collectPartialIndexTables(colDiffs) {
|
|
266
|
+
const tables = [];
|
|
267
|
+
for (const diff of colDiffs) {
|
|
268
|
+
if (diff.actionType !== 'INDEX' || !diff.indexTables) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
for (const indexTable of diff.indexTables) {
|
|
272
|
+
if (this.isPartialIndexTable(indexTable)) {
|
|
273
|
+
tables.push(indexTable);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return tables;
|
|
278
|
+
}
|
|
279
|
+
buildPartialIndexStatement(tableDiff, indexTable) {
|
|
280
|
+
if (this.orm.driverInstance.dbType !== 'postgres') {
|
|
281
|
+
throw new Error('Partial indexes are only supported on postgres.');
|
|
282
|
+
}
|
|
283
|
+
const schema = tableDiff.schema ?? 'public';
|
|
284
|
+
const columns = this.formatIndexColumns(indexTable.properties);
|
|
285
|
+
const statement = `CREATE INDEX "${indexTable.name}" ON "${schema}"."${tableDiff.tableName}" (${columns}) WHERE ${indexTable.where};`;
|
|
286
|
+
return this.ensureStatement(statement);
|
|
287
|
+
}
|
|
288
|
+
ensureStatement(statement) {
|
|
289
|
+
const trimmed = statement.trim();
|
|
290
|
+
if (trimmed.endsWith(';')) {
|
|
291
|
+
return trimmed;
|
|
292
|
+
}
|
|
293
|
+
return `${trimmed};`;
|
|
294
|
+
}
|
|
295
|
+
isPartialIndexTable(indexTable) {
|
|
296
|
+
if (!indexTable.where) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
if (!indexTable.properties) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
return indexTable.properties.length > 0;
|
|
303
|
+
}
|
|
304
|
+
formatIndexColumns(properties) {
|
|
305
|
+
if (!properties) {
|
|
306
|
+
return '';
|
|
307
|
+
}
|
|
308
|
+
return properties.map((prop) => `"${prop}"`).join(', ');
|
|
309
|
+
}
|
|
310
|
+
sortTablesByDependencies(diff) {
|
|
311
|
+
const newTables = diff.filter((d) => d.newTable);
|
|
312
|
+
const alterTables = diff.filter((d) => !d.newTable);
|
|
313
|
+
if (newTables.length === 0) {
|
|
314
|
+
return diff;
|
|
315
|
+
}
|
|
316
|
+
const dependencies = new Map();
|
|
317
|
+
const allTables = new Set();
|
|
318
|
+
newTables.forEach((tableDiff) => {
|
|
319
|
+
allTables.add(tableDiff.tableName);
|
|
320
|
+
dependencies.set(tableDiff.tableName, new Set());
|
|
321
|
+
tableDiff.colDiffs.forEach((colDiff) => {
|
|
322
|
+
if (colDiff.colChanges?.foreignKeys) {
|
|
323
|
+
colDiff.colChanges.foreignKeys.forEach((fk) => {
|
|
324
|
+
dependencies.get(tableDiff.tableName).add(fk.referencedTableName);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
const sorted = [];
|
|
330
|
+
const visited = new Set();
|
|
331
|
+
const visiting = new Set();
|
|
332
|
+
const visit = (tableName) => {
|
|
333
|
+
if (visited.has(tableName))
|
|
334
|
+
return;
|
|
335
|
+
if (visiting.has(tableName)) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
visiting.add(tableName);
|
|
339
|
+
const deps = dependencies.get(tableName);
|
|
340
|
+
if (deps) {
|
|
341
|
+
deps.forEach((dep) => {
|
|
342
|
+
if (allTables.has(dep)) {
|
|
343
|
+
visit(dep);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
visiting.delete(tableName);
|
|
348
|
+
visited.add(tableName);
|
|
349
|
+
const tableDiff = newTables.find((t) => t.tableName === tableName);
|
|
350
|
+
if (tableDiff) {
|
|
351
|
+
sorted.push(tableDiff);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
newTables.forEach((tableDiff) => visit(tableDiff.tableName));
|
|
355
|
+
newTables.forEach((tableDiff) => {
|
|
356
|
+
if (!visited.has(tableDiff.tableName)) {
|
|
357
|
+
sorted.push(tableDiff);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return [...sorted, ...alterTables];
|
|
361
|
+
}
|
|
362
|
+
dropEnumConstraints(tableName, columnName) {
|
|
363
|
+
const possibleConstraintNames = [
|
|
364
|
+
`${tableName}_${columnName}_check`,
|
|
365
|
+
`${columnName}_check`,
|
|
366
|
+
];
|
|
367
|
+
return possibleConstraintNames.map((constraintName) => `alter table "${tableName}" drop constraint if exists "${constraintName}";`);
|
|
368
|
+
}
|
|
369
|
+
assignType(builder, diff, tableDiff) {
|
|
370
|
+
if (diff.actionType === 'ALTER') {
|
|
371
|
+
if (diff.colChanges?.nullable !== undefined) {
|
|
372
|
+
if (diff.colChanges?.nullable) {
|
|
373
|
+
builder.setNullable(diff.colName);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (!diff.colType)
|
|
378
|
+
return;
|
|
379
|
+
const columnName = diff.colName;
|
|
380
|
+
const columnType = diff.colType;
|
|
381
|
+
let columnBuilder;
|
|
382
|
+
if (diff.colChanges?.autoIncrement !== undefined) {
|
|
383
|
+
if (diff.colChanges?.autoIncrement) {
|
|
384
|
+
columnBuilder = builder.increments(diff.colName, {
|
|
385
|
+
primaryKey: diff.colChanges?.primary,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
switch (columnType) {
|
|
391
|
+
case 'varchar':
|
|
392
|
+
columnBuilder = builder.string(columnName, diff.colLength);
|
|
393
|
+
break;
|
|
394
|
+
case 'text':
|
|
395
|
+
columnBuilder = builder.text(columnName);
|
|
396
|
+
break;
|
|
397
|
+
case 'int':
|
|
398
|
+
case 'numeric':
|
|
399
|
+
case 'integer':
|
|
400
|
+
columnBuilder = builder.integer(columnName, diff.colLength);
|
|
401
|
+
break;
|
|
402
|
+
case 'bigint':
|
|
403
|
+
columnBuilder = builder.bigInteger(columnName);
|
|
404
|
+
break;
|
|
405
|
+
case 'double precision':
|
|
406
|
+
case 'double':
|
|
407
|
+
columnBuilder = builder.double(columnName, diff.colChanges?.precision, diff.colChanges?.scale);
|
|
408
|
+
break;
|
|
409
|
+
case 'float4':
|
|
410
|
+
case 'real':
|
|
411
|
+
case 'float':
|
|
412
|
+
columnBuilder = builder.float(columnName, diff.colChanges?.precision, diff.colChanges?.scale);
|
|
413
|
+
break;
|
|
414
|
+
case 'decimal':
|
|
415
|
+
columnBuilder = builder.decimal(columnName, diff.colChanges?.precision, diff.colChanges?.scale);
|
|
416
|
+
break;
|
|
417
|
+
case 'date':
|
|
418
|
+
columnBuilder = builder.date(columnName);
|
|
419
|
+
break;
|
|
420
|
+
case 'datetime':
|
|
421
|
+
columnBuilder = builder.datetime(columnName);
|
|
422
|
+
break;
|
|
423
|
+
case 'time':
|
|
424
|
+
columnBuilder = builder.time(columnName);
|
|
425
|
+
break;
|
|
426
|
+
case 'timestamp':
|
|
427
|
+
columnBuilder = builder.timestamp(columnName, {
|
|
428
|
+
precision: diff.colLength,
|
|
429
|
+
});
|
|
430
|
+
break;
|
|
431
|
+
case 'boolean':
|
|
432
|
+
columnBuilder = builder.boolean(columnName);
|
|
433
|
+
break;
|
|
434
|
+
case 'json':
|
|
435
|
+
columnBuilder = builder.json(columnName);
|
|
436
|
+
break;
|
|
437
|
+
case 'jsonb':
|
|
438
|
+
columnBuilder = builder.jsonb(columnName);
|
|
439
|
+
break;
|
|
440
|
+
case 'enum':
|
|
441
|
+
if (diff.actionType === 'ALTER' && diff.colChanges?.enumItems) {
|
|
442
|
+
columnBuilder = builder
|
|
443
|
+
.text(columnName)
|
|
444
|
+
.checkIn(diff.colChanges.enumItems ?? [], `${columnName}_check`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
columnBuilder = builder.enum(columnName, diff.colChanges.enumItems ?? []);
|
|
448
|
+
}
|
|
449
|
+
break;
|
|
450
|
+
case 'array':
|
|
451
|
+
columnBuilder = builder.specificType(columnName, 'text[]');
|
|
452
|
+
break;
|
|
453
|
+
case 'uuid':
|
|
454
|
+
columnBuilder = builder.uuid(columnName);
|
|
455
|
+
break;
|
|
456
|
+
default:
|
|
457
|
+
columnBuilder = builder.string(columnName, diff.colLength);
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
columnBuilder.notNullable();
|
|
462
|
+
if (diff.colChanges?.nullable !== undefined) {
|
|
463
|
+
if (diff.colChanges?.nullable) {
|
|
464
|
+
columnBuilder.nullable();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (diff.colChanges?.default !== undefined) {
|
|
468
|
+
columnBuilder.defaultTo(diff.colChanges?.default);
|
|
469
|
+
}
|
|
470
|
+
if (typeof diff.colChanges?.primary !== 'undefined') {
|
|
471
|
+
if (diff.colChanges?.primary) {
|
|
472
|
+
columnBuilder.primary();
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
builder.dropPrimary();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return columnBuilder;
|
|
479
|
+
}
|
|
480
|
+
async migrate() {
|
|
481
|
+
await this.startConnection();
|
|
482
|
+
const migrationTable = 'carno_migrations';
|
|
483
|
+
const migrationDirectory = path.join(process.cwd(), this.config.migrationPath ?? 'database/migrations');
|
|
484
|
+
const migrationFiles = fs
|
|
485
|
+
.readdirSync(migrationDirectory)
|
|
486
|
+
.filter((file) => file.endsWith('.sql'))
|
|
487
|
+
.sort();
|
|
488
|
+
if (migrationFiles.length === 0) {
|
|
489
|
+
this.orm.logger.info('No migration files found');
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
await this.orm.transaction(async () => {
|
|
493
|
+
await this.orm.driverInstance.executeSql(`CREATE TABLE IF NOT EXISTS "${migrationTable}" ("migration_file" character varying(255) NOT NULL PRIMARY KEY UNIQUE);`);
|
|
494
|
+
const migrated = await this.orm.driverInstance.executeSql(`SELECT * FROM "${migrationTable}" ORDER BY "migration_file" ASC;`);
|
|
495
|
+
const migratedArray = Array.isArray(migrated) ? migrated : [];
|
|
496
|
+
const lastMigration = migratedArray[migratedArray.length - 1];
|
|
497
|
+
const lastMigrationIndex = migrationFiles.indexOf(lastMigration?.migration_file ?? '');
|
|
498
|
+
const migrationsToExecute = migrationFiles.slice(lastMigrationIndex + 1);
|
|
499
|
+
if (migrationsToExecute.length === 0) {
|
|
500
|
+
this.orm.logger.info('Database is up to date');
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
for (const migrationFile of migrationsToExecute) {
|
|
504
|
+
const migrationFilePath = path.join(migrationDirectory, migrationFile);
|
|
505
|
+
const migrationContent = fs.readFileSync(migrationFilePath, {
|
|
506
|
+
encoding: 'utf-8',
|
|
507
|
+
});
|
|
508
|
+
const sqlInstructions = migrationContent
|
|
509
|
+
.split(';')
|
|
510
|
+
.filter((sql) => sql.trim().length > 0);
|
|
511
|
+
this.orm.logger.info(`Executing migration: ${migrationFile}`);
|
|
512
|
+
for (const sqlInstruction of sqlInstructions) {
|
|
513
|
+
await this.orm.driverInstance.executeSql(sqlInstruction);
|
|
514
|
+
}
|
|
515
|
+
await this.orm.driverInstance.executeSql(`INSERT INTO "${migrationTable}" ("migration_file") VALUES ('${migrationFile}');`);
|
|
516
|
+
this.orm.logger.info(`Migration executed: ${migrationFile}`);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
async generateMigration(configFile = process.cwd(), onlySql = false) {
|
|
521
|
+
await this.startConnection(configFile);
|
|
522
|
+
const directory = path.join(process.cwd(), this.config.migrationPath ?? 'database/migrations');
|
|
523
|
+
const snapshotManager = new snapshot_manager_1.SnapshotManager(directory);
|
|
524
|
+
const snapshotEntities = await this.snapshotEntities();
|
|
525
|
+
const normalizedEntitiesSnapshot = this.normalizeSnapshotTypes(snapshotEntities);
|
|
526
|
+
let previousSnapshot = snapshotManager.loadSnapshot();
|
|
527
|
+
if (!previousSnapshot) {
|
|
528
|
+
previousSnapshot = await this.snapshotBd();
|
|
529
|
+
}
|
|
530
|
+
const normalizedPreviousSnapshot = this.normalizeSnapshotTypes(previousSnapshot);
|
|
531
|
+
const calculator = new diff_calculator_1.DiffCalculator(this.entities, this.orm.driverInstance);
|
|
532
|
+
const diff = calculator.diff(normalizedPreviousSnapshot, normalizedEntitiesSnapshot);
|
|
533
|
+
const sql = this.lastTreatment(await this.run(diff));
|
|
534
|
+
if (onlySql) {
|
|
535
|
+
return sql;
|
|
536
|
+
}
|
|
537
|
+
const fileName = `migration_${new Date().toISOString().replace(/[^\d]/g, '')}` + `.sql`;
|
|
538
|
+
const migrationFilePath = path.join(directory, fileName);
|
|
539
|
+
if (!fs.existsSync(directory)) {
|
|
540
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
541
|
+
}
|
|
542
|
+
const migrationContent = sql.join('\n');
|
|
543
|
+
if (migrationContent.length === 0) {
|
|
544
|
+
this.orm.logger.info('No changes detected');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
fs.writeFileSync(migrationFilePath, migrationContent);
|
|
549
|
+
this.orm.logger.info(`Migration file created: ${migrationFilePath}`);
|
|
550
|
+
snapshotManager.saveSnapshot(normalizedEntitiesSnapshot);
|
|
551
|
+
this.orm.logger.info(`Snapshot updated: ${snapshotManager.getSnapshotPath()}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
lastTreatment(sql) {
|
|
555
|
+
const getDropIndexes = sql.filter((s) => s.startsWith('drop index'));
|
|
556
|
+
const getDropColumns = sql.filter((s) => s.includes('drop column'));
|
|
557
|
+
const dropIndexes = [];
|
|
558
|
+
for (const dropIndex of getDropIndexes) {
|
|
559
|
+
const rawToken = dropIndex.split(' ')[2];
|
|
560
|
+
const indexFullName = rawToken?.replace(/"/g, '');
|
|
561
|
+
if (!indexFullName)
|
|
562
|
+
continue;
|
|
563
|
+
const indexBase = indexFullName.includes('.')
|
|
564
|
+
? indexFullName.split('.').pop() || indexFullName
|
|
565
|
+
: indexFullName;
|
|
566
|
+
const parts = indexBase.split('_');
|
|
567
|
+
let colName;
|
|
568
|
+
if (parts.length >= 3) {
|
|
569
|
+
colName = parts[parts.length - 2];
|
|
570
|
+
}
|
|
571
|
+
else if (parts.length >= 2) {
|
|
572
|
+
colName = parts[0];
|
|
573
|
+
}
|
|
574
|
+
if (!colName) {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const dropColumn = getDropColumns.find((s) => s.includes(colName));
|
|
578
|
+
if (dropColumn) {
|
|
579
|
+
dropIndexes.push(dropIndex);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const sqlFiltered = sql.filter((s) => !dropIndexes.includes(s));
|
|
583
|
+
const seen = new Set();
|
|
584
|
+
const deduped = sqlFiltered.filter((s) => {
|
|
585
|
+
if (seen.has(s))
|
|
586
|
+
return false;
|
|
587
|
+
seen.add(s);
|
|
588
|
+
return true;
|
|
589
|
+
});
|
|
590
|
+
return deduped;
|
|
591
|
+
}
|
|
592
|
+
normalizeSnapshotTypes(snapshot) {
|
|
593
|
+
if (!snapshot)
|
|
594
|
+
return [];
|
|
595
|
+
const normalizeType = (type) => {
|
|
596
|
+
if (!type)
|
|
597
|
+
return type;
|
|
598
|
+
const lower = type.toLowerCase();
|
|
599
|
+
if (lower.startsWith('decimal') || lower.startsWith('numeric')) {
|
|
600
|
+
return 'decimal';
|
|
601
|
+
}
|
|
602
|
+
if (lower === 'double precision' ||
|
|
603
|
+
lower === 'double' ||
|
|
604
|
+
lower === 'real' ||
|
|
605
|
+
lower.startsWith('float')) {
|
|
606
|
+
return 'float';
|
|
607
|
+
}
|
|
608
|
+
return lower;
|
|
609
|
+
};
|
|
610
|
+
return snapshot.map((table) => ({
|
|
611
|
+
...table,
|
|
612
|
+
columns: table.columns.map((col) => ({
|
|
613
|
+
...col,
|
|
614
|
+
type: normalizeType(col.type),
|
|
615
|
+
nullable: col.nullable ?? false,
|
|
616
|
+
})),
|
|
617
|
+
indexes: this.normalizeIndexes(table.indexes),
|
|
618
|
+
uniques: table.uniques ?? [],
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
normalizeIndexes(indexes) {
|
|
622
|
+
if (!indexes) {
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
return indexes.map((index) => this.normalizeIndex(index));
|
|
626
|
+
}
|
|
627
|
+
normalizeIndex(index) {
|
|
628
|
+
if (!this.isIndexDefinition(index.columnName)) {
|
|
629
|
+
return index;
|
|
630
|
+
}
|
|
631
|
+
const parsed = this.parseIndexDefinition(index.columnName);
|
|
632
|
+
return {
|
|
633
|
+
...index,
|
|
634
|
+
columnName: parsed.columns,
|
|
635
|
+
where: parsed.where ?? index.where,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
isIndexDefinition(value) {
|
|
639
|
+
const upper = value.toUpperCase();
|
|
640
|
+
return upper.includes('CREATE INDEX') || upper.includes('CREATE UNIQUE INDEX');
|
|
641
|
+
}
|
|
642
|
+
parseIndexDefinition(definition) {
|
|
643
|
+
const columns = this.extractIndexColumns(definition);
|
|
644
|
+
const where = this.extractIndexWhere(definition);
|
|
645
|
+
return { columns, where };
|
|
646
|
+
}
|
|
647
|
+
extractIndexColumns(definition) {
|
|
648
|
+
const content = this.extractFirstParenContent(definition);
|
|
649
|
+
return this.normalizeIndexColumns(content);
|
|
650
|
+
}
|
|
651
|
+
extractFirstParenContent(definition) {
|
|
652
|
+
const match = definition.match(/\(([^)]+)\)/);
|
|
653
|
+
if (!match) {
|
|
654
|
+
return '';
|
|
655
|
+
}
|
|
656
|
+
return match[1];
|
|
657
|
+
}
|
|
658
|
+
normalizeIndexColumns(value) {
|
|
659
|
+
const tokens = value.split(',');
|
|
660
|
+
const cleaned = tokens.map((token) => this.cleanIndexToken(token));
|
|
661
|
+
return cleaned.join(',');
|
|
662
|
+
}
|
|
663
|
+
cleanIndexToken(token) {
|
|
664
|
+
const trimmed = token.trim();
|
|
665
|
+
return trimmed.replace(/"/g, '');
|
|
666
|
+
}
|
|
667
|
+
extractIndexWhere(definition) {
|
|
668
|
+
const match = definition.match(/WHERE (.+)$/i);
|
|
669
|
+
if (!match) {
|
|
670
|
+
return undefined;
|
|
671
|
+
}
|
|
672
|
+
return match[1].trim();
|
|
673
|
+
}
|
|
674
|
+
async snapshotBd() {
|
|
675
|
+
const snapshot = [];
|
|
676
|
+
for (const [_, values] of this.entities.entries()) {
|
|
677
|
+
const bd = await this.orm.driverInstance.snapshot(values.tableName);
|
|
678
|
+
if (!bd) {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
snapshot.push(bd);
|
|
682
|
+
}
|
|
683
|
+
return snapshot;
|
|
684
|
+
}
|
|
685
|
+
async snapshotEntities() {
|
|
686
|
+
const snapshot = [];
|
|
687
|
+
const processedTables = new Set();
|
|
688
|
+
const tableToEntity = new Map();
|
|
689
|
+
for (const [entityClass, values] of this.entities.entries()) {
|
|
690
|
+
if (!tableToEntity.has(values.tableName)) {
|
|
691
|
+
tableToEntity.set(values.tableName, entityClass);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
for (const [entityClass, values] of this.entities.entries()) {
|
|
695
|
+
if (processedTables.has(values.tableName)) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (tableToEntity.get(values.tableName) !== entityClass) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
const tableSnapshot = await this.entities.snapshot(values);
|
|
702
|
+
snapshot.push(tableSnapshot);
|
|
703
|
+
processedTables.add(values.tableName);
|
|
704
|
+
}
|
|
705
|
+
return snapshot;
|
|
706
|
+
}
|
|
707
|
+
async destroy() {
|
|
708
|
+
if (this.knex) {
|
|
709
|
+
await this.knex.destroy();
|
|
710
|
+
this.knex = null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
exports.Migrator = Migrator;
|
|
715
|
+
//# sourceMappingURL=migrator.js.map
|