sqlmorpheus 0.1.10 → 0.2.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/dist/index.mjs CHANGED
@@ -37,6 +37,47 @@ function getTableInfoFromMetadata(metadata) {
37
37
  return metadata[TABLE_INFO_PROPERTY_NAME];
38
38
  }
39
39
 
40
+ // src/lib/tableInfo/TableObj.ts
41
+ var TableObj = class _TableObj {
42
+ IS_TABLE_OBJ = true;
43
+ tableName;
44
+ columns;
45
+ tableInfo = {
46
+ primaryKey: void 0,
47
+ foreignKeys: [],
48
+ dataTypes: {}
49
+ };
50
+ constructor(tableName, columns) {
51
+ this.tableName = tableName;
52
+ this.columns = columns;
53
+ }
54
+ primaryKey(key) {
55
+ this.tableInfo.primaryKey = key.toString();
56
+ return this;
57
+ }
58
+ foreignKey(fromColumn, toTable, toColumn, options) {
59
+ this.tableInfo.foreignKeys.push({
60
+ fromTable: this.tableName,
61
+ fromColumn: fromColumn.toString(),
62
+ toTable: toTable.tableName,
63
+ toColumn: toColumn.toString(),
64
+ onUpdate: options?.onUpdate,
65
+ onDelete: options?.onDelete
66
+ });
67
+ return this;
68
+ }
69
+ dataType(column, type) {
70
+ this.tableInfo.dataTypes[column.toString()] = type;
71
+ return this;
72
+ }
73
+ static create(tableName, columns) {
74
+ return new _TableObj(tableName, columns);
75
+ }
76
+ static isDbTable(obj) {
77
+ return !!obj?.IS_TABLE_OBJ;
78
+ }
79
+ };
80
+
40
81
  // src/lib/TableStructureGenerator.ts
41
82
  var TableStructureGenerator = class {
42
83
  dialect;
@@ -45,19 +86,21 @@ var TableStructureGenerator = class {
45
86
  this.dialect = dialect;
46
87
  this.dbInstructions = dbInstructions;
47
88
  }
89
+ /**
90
+ * Generates the structure of tables based on database instructions.
91
+ *
92
+ * @return An object where keys are table names, and values are their corresponding table structures.
93
+ */
48
94
  generateTableStructure() {
49
- if (this.dbInstructions.tables.length) {
50
- const tableObjects = {};
51
- for (const table of this.dbInstructions.tables) {
52
- tableObjects[table.name] = { columns: new table(), tableInfo: getTableInfo(table) };
95
+ const tableObjects = {};
96
+ for (const table of this.dbInstructions.tables) {
97
+ if (TableObj.isDbTable(table))
98
+ tableObjects[table.tableName] = table;
99
+ else {
100
+ const classTable = table;
101
+ tableObjects[classTable.name] = { columns: new classTable(), tableInfo: getTableInfo(classTable) };
53
102
  }
54
- return this.getExpectedStructure(tableObjects);
55
- } else {
56
- const tableObjects = this.dbInstructions.tables;
57
- return this.getExpectedStructure(tableObjects);
58
103
  }
59
- }
60
- getExpectedStructure(tableObjects) {
61
104
  const tables = {};
62
105
  for (const tableName in tableObjects) {
63
106
  const obj = tableObjects[tableName];
@@ -65,46 +108,49 @@ var TableStructureGenerator = class {
65
108
  tables[tableName] = {
66
109
  table: tableName,
67
110
  primaryKey: tableInfo?.primaryKey,
68
- columns: this.getColumns(obj.columns, tableInfo?.primaryKey),
111
+ columns: this.getColumns(obj.columns, tableInfo),
69
112
  foreignKeys: tableInfo?.foreignKeys
70
113
  };
71
114
  }
72
115
  return tables;
73
116
  }
74
- getColumns(obj, primaryKey) {
117
+ /**
118
+ * Retrieves the columns for a table
119
+ *
120
+ * @param obj - The table class interface object containing properties that represent the table's fields.
121
+ * @param tableInfo - Optional metadata about the table. Can be undefined.
122
+ * @return An array of {@link ColumnInfo} representing the detailed column information.
123
+ */
124
+ getColumns(obj, tableInfo) {
75
125
  const columns = [];
76
126
  for (const property in obj) {
77
127
  const propertyKey = property;
78
128
  const value = obj[propertyKey];
79
129
  const columnData = {
80
130
  name: property,
81
- isPrimaryKey: property == primaryKey,
82
- type: "TEXT",
83
- defaultValue: this.dialect.typeNull
131
+ isPrimaryKey: property == tableInfo?.primaryKey,
132
+ type: this.dialect.types.string,
133
+ defaultValue: this.dialect.types.null
84
134
  };
85
- switch (typeof value) {
86
- case "string":
87
- columnData.type = this.dialect.typeString;
88
- columnData.defaultValue = value === null ? this.dialect.typeNull : `"${this.dialect.formatValueToSql(value)}"`;
89
- break;
90
- case "bigint":
91
- columnData.type = this.dialect.typeBigInt;
92
- columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
93
- break;
94
- case "number":
95
- columnData.type = this.dialect.typeInt;
96
- columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
97
- break;
98
- case "boolean":
99
- columnData.type = this.dialect.typeBoolean;
100
- columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
101
- break;
135
+ let dataType = tableInfo?.dataTypes?.[property] ?? typeof value;
136
+ switch (dataType) {
102
137
  case "function":
103
138
  continue;
104
- default:
105
- Logger.warn(`${obj.constructor.name}.${property} was skipped because its type is not supported (${typeof value})`);
139
+ case "object":
140
+ if (value instanceof Date) {
141
+ dataType = "date";
142
+ break;
143
+ } else {
144
+ Logger.warn(`${property} was skipped because its type (${typeof value}) is not supported`);
145
+ continue;
146
+ }
147
+ case "undefined":
148
+ case "symbol":
149
+ Logger.warn(`${property} was skipped because its type (${typeof value}) is not supported`);
106
150
  continue;
107
151
  }
152
+ columnData.defaultValue = value === null ? this.dialect.types.null : this.dialect.formatValueToSql(value, dataType);
153
+ columnData.type = this.dialect.types[dataType];
108
154
  columns.push(columnData);
109
155
  }
110
156
  return columns;
@@ -114,60 +160,129 @@ var TableStructureGenerator = class {
114
160
  // src/lib/exceptions/NotAllowedException.ts
115
161
  var NotAllowedException = class extends Error {
116
162
  constructor(entries) {
117
- let message = "Some destructive changes need to be confirmed. In preMigrations() run:";
163
+ let message = "Some destructive changes need to be confirmed. In preMigration() run:\n";
118
164
  for (const entry of entries) {
119
- message += `
120
- migrations.allowMigration(${entry.version}, "${entry.tableName}", "${entry.type}");`;
165
+ message += ` migrations.allowMigration(${entry.version}, ${entry.tableName}, "${entry.type}");
166
+ `;
121
167
  }
122
- super(`${message}
123
-
124
- `);
168
+ super(message);
125
169
  }
126
170
  };
127
171
 
128
- // src/lib/typings/Migrations.ts
172
+ // src/lib/Migrations.ts
129
173
  var Migrations = class {
130
- toVersion = 0;
131
- alwaysAllowed = {};
132
174
  fromVersion = 0;
175
+ toVersion = 0;
176
+ lastUsedVersion = 0;
133
177
  migrationData = {};
178
+ migrationDataForOldTableNames = {};
179
+ alwaysAllowed = {};
134
180
  notAllowedChanges = [];
135
181
  reset(dbInstructions, fromVersion) {
136
182
  this.fromVersion = fromVersion;
137
- this.migrationData = {};
138
183
  this.toVersion = dbInstructions.version;
184
+ this.migrationData = {};
185
+ this.migrationDataForOldTableNames = {};
139
186
  this.alwaysAllowed = dbInstructions.alwaysAllowedMigrations ?? {};
140
187
  this.notAllowedChanges = [];
141
188
  }
142
189
  getEntry(table) {
143
190
  const tableName = this.getTableName(table);
191
+ return this.getEntryFromTableName(tableName);
192
+ }
193
+ getEntryFromTableName(tableName) {
144
194
  if (!this.migrationData.hasOwnProperty(tableName)) {
145
195
  this.migrationData[tableName] = {
146
196
  recreate: false,
147
197
  renamedColumns: [],
148
- allowedMigrations: {}
198
+ allowedMigrations: {},
199
+ usedMigrations: {}
149
200
  };
150
201
  }
151
202
  return this.migrationData[tableName];
152
203
  }
153
204
  getTableName(table) {
154
- return typeof table == "string" ? table : table.name;
155
- }
156
- verifyMigration(tableName, type) {
157
- if (!this.migrationData[tableName]?.allowedMigrations[type] && !this.alwaysAllowed[type])
158
- this.notAllowedChanges.push({ version: this.toVersion, tableName, type });
159
- }
160
- checkIfAllowed(throwIfNotAllowed) {
161
- if (!this.notAllowedChanges.length)
162
- return true;
163
- const exception = new NotAllowedException(this.notAllowedChanges);
164
- if (throwIfNotAllowed)
165
- throw exception;
166
- else {
167
- console.error(exception.message);
205
+ return TableObj.isDbTable(table) ? table.tableName : table.name;
206
+ }
207
+ versionIsRelevant(version) {
208
+ if (version < this.fromVersion || version > this.toVersion)
168
209
  return false;
210
+ if (version < this.lastUsedVersion)
211
+ throw new Error("Migrations in preMigration() have to be ordered by version (beginning with the lowest version).");
212
+ this.lastUsedVersion = version;
213
+ return true;
214
+ }
215
+ verifyRenamingTasks(newTables) {
216
+ for (const tableName in this.migrationData) {
217
+ const migrationEntry = this.migrationData[tableName];
218
+ if (migrationEntry.tableRenaming) {
219
+ if (migrationEntry.tableRenaming.oldName == tableName)
220
+ return new Error(`You set table "${tableName}" to be renamed to itself!`);
221
+ if (migrationEntry.tableRenaming.newName != tableName)
222
+ return new Error(`You set table "${tableName}" to be renamed to a different name than the one in your structure (${migrationEntry.tableRenaming.newName})!`);
223
+ else if (!newTables[tableName])
224
+ return new Error(`You set table "${migrationEntry.tableRenaming}" migrations for "${tableName}". But "${tableName}" does not exist in your structure.`);
225
+ }
226
+ for (const renamingData of migrationEntry.renamedColumns) {
227
+ const oldColumnName = renamingData.oldName;
228
+ const newColumnName = renamingData.newName;
229
+ if (oldColumnName == newColumnName)
230
+ return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to itself!`);
231
+ else if (!newTables[tableName]?.columns.find((column) => column.name == newColumnName))
232
+ return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to ${tableName}.${newColumnName}. But ${tableName}.${newColumnName} does not exist in your structure.`);
233
+ }
234
+ }
235
+ }
236
+ verifyAllowedMigrations() {
237
+ if (this.notAllowedChanges.length)
238
+ return new NotAllowedException(this.notAllowedChanges);
239
+ let errorMsg = "";
240
+ for (const tableName in this.migrationData) {
241
+ const migration = this.migrationData[tableName];
242
+ for (const type in migration.allowedMigrations) {
243
+ if (!migration.usedMigrations[type])
244
+ errorMsg += `Migration "${type}" for ${tableName} was allowed but not needed.
245
+ `;
246
+ }
247
+ }
248
+ if (errorMsg)
249
+ return new Error(errorMsg);
250
+ }
251
+ compareWithAllowedMigration(tableName, type) {
252
+ const migrationEntry = this.migrationData[tableName];
253
+ if (!migrationEntry?.allowedMigrations[type]) {
254
+ if (!this.alwaysAllowed[type])
255
+ this.notAllowedChanges.push({ version: this.toVersion, tableName, type });
256
+ } else if (migrationEntry)
257
+ migrationEntry.usedMigrations[type] = true;
258
+ }
259
+ getMigrationData() {
260
+ return this.migrationData;
261
+ }
262
+ loopRenamedColumns(tableName, callback) {
263
+ const migrationEntry = this.migrationData[tableName];
264
+ for (const renamingData of migrationEntry.renamedColumns) {
265
+ callback(renamingData.oldName, renamingData.newName);
169
266
  }
170
267
  }
268
+ getNewestTableName(tableName) {
269
+ return this.migrationDataForOldTableNames[tableName]?.tableRenaming?.newName ?? tableName;
270
+ }
271
+ getOldTableName(tableName) {
272
+ return this.migrationData[tableName]?.tableRenaming?.oldName ?? tableName;
273
+ }
274
+ getNewestColumnName(tableName, oldColumnName) {
275
+ const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns.oldName == oldColumnName);
276
+ return columnNames ? columnNames.newName : oldColumnName;
277
+ }
278
+ willBeRecreated(tableName) {
279
+ return this.migrationData[tableName]?.recreate;
280
+ }
281
+ internalRecreate(tableName, reason) {
282
+ const entry = this.getEntryFromTableName(tableName);
283
+ entry.recreate = true;
284
+ Logger.log(`Table ${tableName} will be recreated(${reason})!`);
285
+ }
171
286
  allowMigration(version, table, ...allowedMigrations) {
172
287
  if (version < this.fromVersion || version > this.toVersion)
173
288
  return;
@@ -176,52 +291,33 @@ var Migrations = class {
176
291
  entry.allowedMigrations[key] = true;
177
292
  }
178
293
  }
179
- renameTable(version, oldTableName, newTable) {
180
- if (version < this.fromVersion || version > this.toVersion)
294
+ renameTable(version, oldTableName, newTableName) {
295
+ if (!this.versionIsRelevant(version))
181
296
  return;
182
- const entry = this.getEntry(newTable);
183
- entry.recreate = true;
184
- if (!entry.oldTableName)
185
- entry.oldTableName = oldTableName;
297
+ const newTableNameString = this.getTableName(newTableName);
298
+ const entry = this.getEntry(newTableName);
299
+ this.migrationDataForOldTableNames[oldTableName] = entry;
300
+ if (!entry.tableRenaming)
301
+ entry.tableRenaming = { oldName: oldTableName, newName: newTableNameString };
302
+ else
303
+ entry.tableRenaming.newName = newTableNameString;
186
304
  }
187
305
  renameColumn(version, table, oldColumn, newColumn) {
188
- if (version < this.fromVersion || version > this.toVersion)
306
+ if (!this.versionIsRelevant(version))
189
307
  return;
190
308
  const entry = this.getEntry(table);
191
- const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2[entry2.length - 1] == oldColumn);
309
+ const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2.newName == oldColumn);
192
310
  if (existingColumnEntry)
193
- existingColumnEntry.push(newColumn);
311
+ existingColumnEntry.newName = newColumn;
194
312
  else
195
- entry.renamedColumns.push([oldColumn, newColumn]);
196
- }
197
- recreateTableImp(table) {
198
- const entry = this.getEntry(table);
199
- entry.recreate = true;
313
+ entry.renamedColumns.push({ oldName: oldColumn, newName: newColumn });
200
314
  }
201
315
  recreateTable(version, table) {
202
- if (version < this.fromVersion || version > this.toVersion)
316
+ if (!this.versionIsRelevant(version))
203
317
  return;
204
- this.recreateTableImp(table);
205
- }
206
- getMigrationData() {
207
- return this.migrationData;
208
- }
209
- loopRenamedColumns(tableName, callback) {
210
- const migrationEntry = this.migrationData[tableName];
211
- for (const renamingArray of migrationEntry.renamedColumns) {
212
- if (renamingArray.length <= 1)
213
- continue;
214
- const oldColumnName = renamingArray[0];
215
- const newColumnName = renamingArray[renamingArray.length - 1];
216
- callback(oldColumnName, newColumnName);
217
- }
218
- }
219
- getUpdatedColumnName(tableName, oldColumnName) {
220
- const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns[0] == oldColumnName);
221
- return columnNames ? columnNames[columnNames.length - 1] : oldColumnName;
222
- }
223
- willBeRecreated(tableName) {
224
- return this.migrationData[tableName]?.recreate;
318
+ const entry = this.getEntry(table);
319
+ entry.recreate = true;
320
+ Logger.log(`Table ${this.getTableName(table)} will be recreated!`);
225
321
  }
226
322
  };
227
323
 
@@ -229,6 +325,10 @@ var Migrations = class {
229
325
  var MigrationManager = class {
230
326
  migrations = new Migrations();
231
327
  newTables = {};
328
+ /**
329
+ * Table names that currently exist in the database
330
+ * Note: If tables are set to be renamed, then names in this array will be corrected to their future name.
331
+ */
232
332
  existingTables = [];
233
333
  dialect;
234
334
  constructor(dialect) {
@@ -286,8 +386,9 @@ ${preSQL.down}`;
286
386
  changes.down += initialChanges.down;
287
387
  } else {
288
388
  Logger.log(`Creating migration SQL from version ${fromVersion} to ${toVersion}`);
289
- this.existingTables = await this.dialect.getTableNames();
389
+ this.existingTables = (await this.dialect.getTableNames()).map((oldTableName) => this.migrations.getNewestTableName(oldTableName));
290
390
  [
391
+ this.renameTables(),
291
392
  await this.createAndDropTables(),
292
393
  await this.migrateForeignKeys(),
293
394
  await this.migrateColumns(),
@@ -316,8 +417,18 @@ ${foreignKeyOnQuery}`;
316
417
  changes.down += `
317
418
 
318
419
  ${foreignKeyOnQuery}`;
319
- if (!this.migrations.checkIfAllowed(dbInstructions.throwIfNotAllowed)) {
320
- return null;
420
+ const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
421
+ if (exception) {
422
+ Logger.debug(`
423
+ Canceled changes:
424
+ ${changes.up}
425
+ `);
426
+ if (dbInstructions.throwIfNotAllowed)
427
+ throw exception;
428
+ else {
429
+ console.error(exception.message);
430
+ return null;
431
+ }
321
432
  }
322
433
  return {
323
434
  fromVersion,
@@ -325,6 +436,22 @@ ${foreignKeyOnQuery}`;
325
436
  changes
326
437
  };
327
438
  }
439
+ renameTables() {
440
+ const changes = {
441
+ up: "\n\n-- Rename tables\n",
442
+ down: "\n\n-- Rename tables\n"
443
+ };
444
+ const data = this.migrations.getMigrationData();
445
+ for (const tableName in data) {
446
+ const migrationEntry = data[tableName];
447
+ if (migrationEntry.tableRenaming) {
448
+ Logger.log(`Table "${migrationEntry.tableRenaming.oldName}" will be renamed to "${tableName}"!`);
449
+ changes.up += this.dialect.renameTable(migrationEntry.tableRenaming.oldName, tableName);
450
+ changes.down += this.dialect.renameTable(tableName, migrationEntry.tableRenaming.oldName);
451
+ }
452
+ }
453
+ return changes;
454
+ }
328
455
  /**
329
456
  * Generates SQL change statements for creating and dropping tables based on the current table structure
330
457
  * and existing table configurations in the database. The `up` changes include SQL statements for creating
@@ -336,8 +463,8 @@ ${foreignKeyOnQuery}`;
336
463
  */
337
464
  async createAndDropTables() {
338
465
  let changes = {
339
- up: "\n\n-- Create tables\n",
340
- down: "\n\n-- Create tables\n"
466
+ up: "\n\n-- Create and drop tables\n",
467
+ down: "\n\n-- Create and drop tables\n"
341
468
  };
342
469
  for (const tableName in this.newTables) {
343
470
  if (this.tableDoesNotExists(tableName)) {
@@ -350,12 +477,13 @@ ${foreignKeyOnQuery}`;
350
477
  }
351
478
  for (const tableName of this.existingTables) {
352
479
  if (!this.newTables[tableName]) {
353
- this.migrations.verifyMigration(tableName, "dropTable");
480
+ this.migrations.compareWithAllowedMigration(tableName, "dropTable");
354
481
  if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
355
- this.migrations.verifyMigration(tableName, "continueWithoutRollback");
482
+ this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
483
+ const dbTableName = this.migrations.getOldTableName(tableName);
356
484
  changes.up += `${this.dialect.dropTable(tableName)}
357
485
  `;
358
- changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(tableName, await this.dialect.getColumnInformation(tableName), await this.dialect.getForeignKeys(tableName)) : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
486
+ changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(tableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName)) : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
359
487
  }
360
488
  }
361
489
  return changes;
@@ -406,10 +534,10 @@ ${foreignKeyOnQuery}`;
406
534
  const migrationEntry = migrationData[tableName];
407
535
  if (!migrationEntry.recreate)
408
536
  continue;
409
- this.migrations.verifyMigration(tableName, "recreateTable");
537
+ this.migrations.compareWithAllowedMigration(tableName, "recreateTable");
410
538
  if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
411
- this.migrations.verifyMigration(tableName, "continueWithoutRollback");
412
- const oldColumnList = await this.dialect.getColumnInformation(tableName);
539
+ this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
540
+ const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
413
541
  const newColumnList = this.newTables[tableName].columns;
414
542
  const moveableColumns = newColumnList.filter((entry) => oldColumnList.find((oldEntry) => oldEntry.name == entry.name) != void 0).map((entry) => entry.name);
415
543
  const backupTableName = `${tableName}__backup`;
@@ -419,8 +547,9 @@ ${foreignKeyOnQuery}`;
419
547
  );
420
548
  const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
421
549
  const structure = this.newTables[tableName];
422
- changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery;
423
- changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(tableName), await this.dialect.getForeignKeys(tableName)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
550
+ changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery + "\n";
551
+ const dbTableName = this.migrations.getOldTableName(tableName);
552
+ changes.down += this.dialect.canInspectForeignKeys && this.dialect.canInspectPrimaryKey ? this.createTableSql(backupTableName, await this.dialect.getColumnInformation(dbTableName), await this.dialect.getForeignKeys(dbTableName)) + "\n" + moveDataQuery : "\n\n-- Cannot recreate foreign keys or load primary keys with this database! Table will not be recreated!\n";
424
553
  }
425
554
  return changes;
426
555
  }
@@ -428,8 +557,6 @@ ${foreignKeyOnQuery}`;
428
557
  * Migrates foreign key constraints for all defined tables.
429
558
  */
430
559
  async migrateForeignKeys() {
431
- if (!this.dialect.canAlterForeignKeys)
432
- return { up: "", down: "" };
433
560
  let changes = {
434
561
  up: "\n\n-- Foreign keys\n",
435
562
  down: "\n\n-- Foreign keys\n"
@@ -439,13 +566,13 @@ ${foreignKeyOnQuery}`;
439
566
  continue;
440
567
  const structure = this.newTables[tableName];
441
568
  const newForeignKeys = structure.foreignKeys ?? [];
442
- const oldForeignKeys = await this.dialect.getForeignKeys(tableName);
569
+ const oldForeignKeys = await this.dialect.getForeignKeys(this.migrations.getOldTableName(tableName));
443
570
  let checkForNewForeignKeys = true;
444
571
  for (const oldForeignKey of oldForeignKeys) {
445
572
  const newForeignKey = newForeignKeys.find((entry) => entry.fromColumn == oldForeignKey.fromColumn);
446
573
  if (!newForeignKey) {
447
- Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was removed!`);
448
- this.migrations.verifyMigration(tableName, "removeForeignKey");
574
+ Logger.log(`Foreign key ${oldForeignKey.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be removed!`);
575
+ this.migrations.compareWithAllowedMigration(tableName, "removeForeignKey");
449
576
  if (this.dialect.canAlterForeignKeys) {
450
577
  changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn);
451
578
  changes.down += this.dialect.addForeignKey(
@@ -453,13 +580,13 @@ ${foreignKeyOnQuery}`;
453
580
  this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
454
581
  );
455
582
  } else {
456
- this.migrations.recreateTableImp(structure.table);
583
+ this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
457
584
  checkForNewForeignKeys = false;
458
585
  }
459
586
  } else {
460
587
  if (!newForeignKey || oldForeignKey.toColumn != newForeignKey.toColumn || (oldForeignKey.onUpdate ?? "NO ACTION") != (newForeignKey.onUpdate ?? "NO ACTION") || (oldForeignKey.onDelete ?? "NO ACTION") != (newForeignKey.onDelete ?? "NO ACTION")) {
461
- Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was changed!`);
462
- this.migrations.verifyMigration(tableName, "alterForeignKey");
588
+ Logger.log(`Foreign key ${oldForeignKey.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be changed!`);
589
+ this.migrations.compareWithAllowedMigration(tableName, "alterForeignKey");
463
590
  if (this.dialect.canAlterForeignKeys) {
464
591
  changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn) + this.dialect.addForeignKey(
465
592
  newForeignKey.fromTable,
@@ -470,7 +597,7 @@ ${foreignKeyOnQuery}`;
470
597
  this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
471
598
  ) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
472
599
  } else {
473
- this.migrations.recreateTableImp(structure.table);
600
+ this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
474
601
  checkForNewForeignKeys = false;
475
602
  }
476
603
  }
@@ -481,7 +608,7 @@ ${foreignKeyOnQuery}`;
481
608
  const oldForeignKey = oldForeignKeys.find((entry) => entry.fromColumn == newForeignKey.fromColumn);
482
609
  if (oldForeignKey)
483
610
  continue;
484
- Logger.log(`Foreign key ${newForeignKey.toTable}.${newForeignKey.toColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} is new!`);
611
+ Logger.log(`Foreign key ${newForeignKey.fromTable}.${newForeignKey.fromColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} will be added!`);
485
612
  if (this.dialect.canAlterForeignKeys) {
486
613
  changes.up += this.dialect.addForeignKey(
487
614
  newForeignKey.toTable,
@@ -489,7 +616,7 @@ ${foreignKeyOnQuery}`;
489
616
  );
490
617
  changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
491
618
  } else {
492
- this.migrations.recreateTableImp(structure.table);
619
+ this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
493
620
  break;
494
621
  }
495
622
  }
@@ -510,13 +637,13 @@ ${foreignKeyOnQuery}`;
510
637
  if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
511
638
  continue;
512
639
  const newTableDefinition = this.newTables[tableName];
513
- const oldColumnList = await this.dialect.getColumnInformation(tableName);
640
+ const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
514
641
  const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
515
642
  const newColumnList = newTableDefinition.columns;
516
643
  const newPrimaryKey = newTableDefinition.primaryKey;
517
644
  if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
518
- Logger.log(`Primary key in ${tableName} was changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
519
- this.migrations.verifyMigration(tableName, "alterPrimaryKey");
645
+ Logger.log(`Primary key in ${tableName} will be changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
646
+ this.migrations.compareWithAllowedMigration(tableName, "alterPrimaryKey");
520
647
  if (this.dialect.canAlterPrimaryKey) {
521
648
  if (oldPrimaryKey) {
522
649
  changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
@@ -527,26 +654,26 @@ ${foreignKeyOnQuery}`;
527
654
  changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
528
655
  }
529
656
  } else
530
- this.migrations.recreateTableImp(newTableDefinition.table);
657
+ this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
531
658
  continue;
532
659
  }
533
660
  for (const newColumn of newColumnList) {
534
661
  const oldColumn = oldColumnList.find(
535
- (entry) => this.migrations.getUpdatedColumnName(tableName, entry.name) == newColumn.name
662
+ (entry) => this.migrations.getNewestColumnName(tableName, entry.name) == newColumn.name
536
663
  );
537
664
  if (oldColumn == void 0) {
538
665
  changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.defaultValue, newColumn.isPrimaryKey)) + "\n";
539
666
  changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
540
667
  } else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
541
- this.migrations.recreateTableImp(newTableDefinition.table);
668
+ this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
542
669
  }
543
670
  }
544
671
  for (const oldColumn of oldColumnList) {
545
672
  const newColumn = newColumnList.find(
546
- (entry) => entry.name == this.migrations.getUpdatedColumnName(tableName, oldColumn.name)
673
+ (entry) => entry.name == this.migrations.getNewestColumnName(tableName, oldColumn.name)
547
674
  );
548
675
  if (newColumn == void 0) {
549
- this.migrations.verifyMigration(tableName, "dropColumn");
676
+ this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
550
677
  changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
551
678
  changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.defaultValue, oldColumn.isPrimaryKey)) + "\n";
552
679
  }
@@ -580,6 +707,7 @@ ${foreignKeyOnQuery}`;
580
707
  if (migrationData[tableName].recreate)
581
708
  continue;
582
709
  this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
710
+ Logger.log(`Column ${tableName}.${oldColumnName} will be renamed to ${tableName}.${newColumnName}!`);
583
711
  changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
584
712
  changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
585
713
  });
@@ -646,24 +774,37 @@ ${changes.down}`, { encoding: "utf-8" });
646
774
  }
647
775
  };
648
776
 
649
- // src/lib/tableInfo/decorators/DbTable.ts
777
+ // src/lib/tableInfo/decorators/TableClass.ts
650
778
  import "polyfill-symbol-metadata";
651
- function DbTable(tableName, primaryKey) {
779
+ function TableClass(tableName, primaryKey) {
652
780
  return (table, context) => {
653
- Object.defineProperty(table, "name", { value: tableName, writable: false });
654
781
  const tableInfo = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
655
782
  tableInfo.primaryKey = primaryKey?.toString();
656
783
  if (tableInfo.foreignKeys) {
657
784
  for (const foreignKey of tableInfo.foreignKeys) {
658
785
  foreignKey.fromTable = tableName;
786
+ if (foreignKey.toTable == table.name)
787
+ foreignKey.toTable = tableName;
659
788
  }
660
789
  }
790
+ Object.defineProperty(table, "name", { value: tableName, writable: false });
791
+ };
792
+ }
793
+
794
+ // src/lib/tableInfo/decorators/DataType.ts
795
+ import "polyfill-symbol-metadata";
796
+ function DataType(type) {
797
+ return (table, context) => {
798
+ const metadata = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
799
+ if (!metadata.dataTypes)
800
+ metadata.dataTypes = {};
801
+ metadata.dataTypes[context.name ?? context] = type;
661
802
  };
662
803
  }
663
804
 
664
805
  // src/lib/tableInfo/decorators/ForeignKey.ts
665
806
  import "polyfill-symbol-metadata";
666
- function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
807
+ function ForeignKey(toTable, toColumn, options) {
667
808
  return (table, context) => {
668
809
  const metadata = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
669
810
  if (!metadata.foreignKeys)
@@ -674,8 +815,8 @@ function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
674
815
  //to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
675
816
  toTable: toTable.name,
676
817
  toColumn: toColumn.toString(),
677
- onDelete,
678
- onUpdate
818
+ onDelete: options?.onDelete,
819
+ onUpdate: options?.onUpdate
679
820
  });
680
821
  };
681
822
  }
@@ -688,32 +829,108 @@ var DefaultSql = class {
688
829
  canAlterPrimaryKey = false;
689
830
  canInspectForeignKeys = false;
690
831
  canInspectPrimaryKey = false;
691
- typeString = "TEXT";
692
- typeInt = "INTEGER";
693
- typeBigInt = "BIGINT";
694
- typeBoolean = "BOOLEAN";
695
- typeNull = "NULL";
832
+ types = {
833
+ string: "TEXT",
834
+ number: "INTEGER",
835
+ bigint: "BIGINT",
836
+ boolean: "BOOLEAN",
837
+ date: "DATE",
838
+ time: "TIME",
839
+ dateTime: "DATETIME",
840
+ null: "NULL"
841
+ };
696
842
  constructor(db) {
697
843
  this.db = db;
698
844
  }
699
- formatValueToSql(value) {
700
- return value.toString();
845
+ /**
846
+ * Formats a given value to a SQL-equivalent string based on the provided data type.
847
+ *
848
+ * @param value - The value to be formatted.
849
+ * @param type - The data type of the value. Either determined through typeof or {@link TableInfo} setting
850
+ * @return A string representation of the value formatted for SQL.
851
+ */
852
+ formatValueToSql(value, type) {
853
+ function toDate(value2) {
854
+ return value2 instanceof Date ? value2 : new Date(value2);
855
+ }
856
+ switch (type) {
857
+ case "string":
858
+ return `"${value}"`;
859
+ case "time":
860
+ return toDate(value).toISOString().slice(11, 19);
861
+ case "date":
862
+ return toDate(value).toISOString().slice(0, 10);
863
+ case "dateTime":
864
+ return toDate(value).toISOString().slice(0, 19).replace("T", " ");
865
+ default:
866
+ return value.toString();
867
+ }
701
868
  }
869
+ /**
870
+ * Query to enable / disable foreign keys in the database.
871
+ * Is dialect specific.
872
+ *
873
+ * @param enabled - A boolean indicating whether to enable or disable foreign key constraints.
874
+ * @return The query
875
+ */
702
876
  changeForeignKeysState(enabled) {
703
877
  return "";
704
878
  }
879
+ /**
880
+ * Query to add a foreign key to a specified table.
881
+ * Is only used if {@link canAlterForeignKeys} is true.
882
+ *
883
+ * @param fromTableName - The name of the table to which the foreign key will be added.
884
+ * @param foreignKey - The foreign key definition to be added, see {@link foreignKey()}.
885
+ * @return The SQL statement that adds the foreign key to the specified table.
886
+ */
705
887
  addForeignKey(fromTableName, foreignKey) {
706
888
  throw new Error("Adding foreign keys is not supported!");
707
889
  }
890
+ /**
891
+ * Query to remove a foreign key from a specified table.
892
+ * Is only used if {@link canAlterForeignKeys} is true.
893
+ *
894
+ * @param tableName - The name of the table to which the foreign key will be added.
895
+ * @param foreignKeyName - The name of the foreign key definition.
896
+ * @return The SQL statement that removes the foreign key from the specified table.
897
+ */
708
898
  removeForeignKey(tableName, foreignKeyName) {
709
899
  throw new Error("Foreign key removal is not supported!");
710
900
  }
901
+ /**
902
+ * Query to add a primary key to the specified table.
903
+ * Is only used if {@link canAlterPrimaryKey} is true.
904
+ *
905
+ * @param tableName - The name of the table to which the primary key will be added.
906
+ * @param columnName - The name of the column that will be set as the primary key.
907
+ * @return A SQL statement to add a primary key to the specified table and column.
908
+ */
711
909
  addPrimaryKey(tableName, columnName) {
712
910
  throw new Error("Adding foreign keys is not supported!");
713
911
  }
912
+ /**
913
+ * Query to remove a primary key from the specified table.
914
+ * Is only used if {@link canAlterPrimaryKey} is true.
915
+ *
916
+ * @param tableName - The name of the table to which the primary key will be added.
917
+ * @param columnName - The name of the column that will be set as the primary key.
918
+ * @return A SQL statement to remove a primary key from the specified table and column.
919
+ */
714
920
  removePrimaryKey(tableName, columnName) {
715
921
  throw new Error("Foreign key removal is not supported!");
716
922
  }
923
+ /**
924
+ * Constructs a SQL FOREIGN KEY constraint clause.
925
+ * Used in combination with {@link addForeignKey} or {@link createTable}
926
+ *
927
+ * @param column - The name of the column in the current table to apply the foreign key constraint.
928
+ * @param foreignTable - The name of the referenced table.
929
+ * @param foreignColumn - The name of the referenced column in the foreign table.
930
+ * @param onUpdate - Optional action to specify the behavior on update (e.g., CASCADE, SET NULL).
931
+ * @param onDelete - Optional action to specify the behavior on delete (e.g., CASCADE, SET NULL).
932
+ * @return The SQL string representing the foreign key constraint.
933
+ */
717
934
  foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
718
935
  let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
719
936
  if (onUpdate)
@@ -763,7 +980,7 @@ var DefaultSql = class {
763
980
  }
764
981
  migrationTableQuery() {
765
982
  return this.createTable(MIGRATION_DATA_TABLE_NAME, [
766
- this.columnDefinition("version", this.typeInt, "0", false)
983
+ this.columnDefinition("version", this.types.number, "0", false)
767
984
  ]);
768
985
  }
769
986
  async getForeignKeys(tableName) {
@@ -792,8 +1009,16 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
792
1009
 
793
1010
  // src/lib/dialects/SqliteDialect.ts
794
1011
  var SqliteDialect = class extends DefaultSql {
795
- typeBoolean = "INTEGER";
796
- typeBigInt = "INTEGER";
1012
+ types = {
1013
+ string: "TEXT",
1014
+ number: "INTEGER",
1015
+ bigint: "INTEGER",
1016
+ boolean: "INTEGER",
1017
+ date: "DATE",
1018
+ time: "TIME",
1019
+ dateTime: "DATETIME",
1020
+ null: "NULL"
1021
+ };
797
1022
  canAlterForeignKeys = false;
798
1023
  canAlterPrimaryKey = false;
799
1024
  canInspectForeignKeys = true;
@@ -815,12 +1040,19 @@ var SqliteDialect = class extends DefaultSql {
815
1040
  };
816
1041
  });
817
1042
  }
818
- formatValueToSql(value) {
819
- switch (typeof value) {
1043
+ formatValueToSql(value, type) {
1044
+ function toDate(value2) {
1045
+ return value2 instanceof Date ? value2 : new Date(value2);
1046
+ }
1047
+ switch (type) {
820
1048
  case "boolean":
821
1049
  return value ? "1" : "0";
1050
+ case "date":
1051
+ case "time":
1052
+ case "dateTime":
1053
+ return toDate(value).getTime().toString();
822
1054
  default:
823
- return value.toString();
1055
+ return super.formatValueToSql(value, type);
824
1056
  }
825
1057
  }
826
1058
  async getForeignKeys(tableName) {
@@ -1057,8 +1289,10 @@ async function rollback(db, dbInstructions, toVersion) {
1057
1289
  await dialect.setVersion(toVersion);
1058
1290
  }
1059
1291
  export {
1060
- DbTable,
1292
+ DataType,
1061
1293
  ForeignKey,
1294
+ TableClass,
1295
+ TableObj,
1062
1296
  prepareAndRunMigration,
1063
1297
  prepareMigration,
1064
1298
  rollback,