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.js CHANGED
@@ -20,8 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- DbTable: () => DbTable,
23
+ DataType: () => DataType,
24
24
  ForeignKey: () => ForeignKey,
25
+ TableClass: () => TableClass,
26
+ TableObj: () => TableObj,
25
27
  prepareAndRunMigration: () => prepareAndRunMigration,
26
28
  prepareMigration: () => prepareMigration,
27
29
  rollback: () => rollback,
@@ -69,6 +71,47 @@ function getTableInfoFromMetadata(metadata) {
69
71
  return metadata[TABLE_INFO_PROPERTY_NAME];
70
72
  }
71
73
 
74
+ // src/lib/tableInfo/TableObj.ts
75
+ var TableObj = class _TableObj {
76
+ IS_TABLE_OBJ = true;
77
+ tableName;
78
+ columns;
79
+ tableInfo = {
80
+ primaryKey: void 0,
81
+ foreignKeys: [],
82
+ dataTypes: {}
83
+ };
84
+ constructor(tableName, columns) {
85
+ this.tableName = tableName;
86
+ this.columns = columns;
87
+ }
88
+ primaryKey(key) {
89
+ this.tableInfo.primaryKey = key.toString();
90
+ return this;
91
+ }
92
+ foreignKey(fromColumn, toTable, toColumn, options) {
93
+ this.tableInfo.foreignKeys.push({
94
+ fromTable: this.tableName,
95
+ fromColumn: fromColumn.toString(),
96
+ toTable: toTable.tableName,
97
+ toColumn: toColumn.toString(),
98
+ onUpdate: options?.onUpdate,
99
+ onDelete: options?.onDelete
100
+ });
101
+ return this;
102
+ }
103
+ dataType(column, type) {
104
+ this.tableInfo.dataTypes[column.toString()] = type;
105
+ return this;
106
+ }
107
+ static create(tableName, columns) {
108
+ return new _TableObj(tableName, columns);
109
+ }
110
+ static isDbTable(obj) {
111
+ return !!obj?.IS_TABLE_OBJ;
112
+ }
113
+ };
114
+
72
115
  // src/lib/TableStructureGenerator.ts
73
116
  var TableStructureGenerator = class {
74
117
  dialect;
@@ -77,19 +120,21 @@ var TableStructureGenerator = class {
77
120
  this.dialect = dialect;
78
121
  this.dbInstructions = dbInstructions;
79
122
  }
123
+ /**
124
+ * Generates the structure of tables based on database instructions.
125
+ *
126
+ * @return An object where keys are table names, and values are their corresponding table structures.
127
+ */
80
128
  generateTableStructure() {
81
- if (this.dbInstructions.tables.length) {
82
- const tableObjects = {};
83
- for (const table of this.dbInstructions.tables) {
84
- tableObjects[table.name] = { columns: new table(), tableInfo: getTableInfo(table) };
129
+ const tableObjects = {};
130
+ for (const table of this.dbInstructions.tables) {
131
+ if (TableObj.isDbTable(table))
132
+ tableObjects[table.tableName] = table;
133
+ else {
134
+ const classTable = table;
135
+ tableObjects[classTable.name] = { columns: new classTable(), tableInfo: getTableInfo(classTable) };
85
136
  }
86
- return this.getExpectedStructure(tableObjects);
87
- } else {
88
- const tableObjects = this.dbInstructions.tables;
89
- return this.getExpectedStructure(tableObjects);
90
137
  }
91
- }
92
- getExpectedStructure(tableObjects) {
93
138
  const tables = {};
94
139
  for (const tableName in tableObjects) {
95
140
  const obj = tableObjects[tableName];
@@ -97,46 +142,49 @@ var TableStructureGenerator = class {
97
142
  tables[tableName] = {
98
143
  table: tableName,
99
144
  primaryKey: tableInfo?.primaryKey,
100
- columns: this.getColumns(obj.columns, tableInfo?.primaryKey),
145
+ columns: this.getColumns(obj.columns, tableInfo),
101
146
  foreignKeys: tableInfo?.foreignKeys
102
147
  };
103
148
  }
104
149
  return tables;
105
150
  }
106
- getColumns(obj, primaryKey) {
151
+ /**
152
+ * Retrieves the columns for a table
153
+ *
154
+ * @param obj - The table class interface object containing properties that represent the table's fields.
155
+ * @param tableInfo - Optional metadata about the table. Can be undefined.
156
+ * @return An array of {@link ColumnInfo} representing the detailed column information.
157
+ */
158
+ getColumns(obj, tableInfo) {
107
159
  const columns = [];
108
160
  for (const property in obj) {
109
161
  const propertyKey = property;
110
162
  const value = obj[propertyKey];
111
163
  const columnData = {
112
164
  name: property,
113
- isPrimaryKey: property == primaryKey,
114
- type: "TEXT",
115
- defaultValue: this.dialect.typeNull
165
+ isPrimaryKey: property == tableInfo?.primaryKey,
166
+ type: this.dialect.types.string,
167
+ defaultValue: this.dialect.types.null
116
168
  };
117
- switch (typeof value) {
118
- case "string":
119
- columnData.type = this.dialect.typeString;
120
- columnData.defaultValue = value === null ? this.dialect.typeNull : `"${this.dialect.formatValueToSql(value)}"`;
121
- break;
122
- case "bigint":
123
- columnData.type = this.dialect.typeBigInt;
124
- columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
125
- break;
126
- case "number":
127
- columnData.type = this.dialect.typeInt;
128
- columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
129
- break;
130
- case "boolean":
131
- columnData.type = this.dialect.typeBoolean;
132
- columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
133
- break;
169
+ let dataType = tableInfo?.dataTypes?.[property] ?? typeof value;
170
+ switch (dataType) {
134
171
  case "function":
135
172
  continue;
136
- default:
137
- Logger.warn(`${obj.constructor.name}.${property} was skipped because its type is not supported (${typeof value})`);
173
+ case "object":
174
+ if (value instanceof Date) {
175
+ dataType = "date";
176
+ break;
177
+ } else {
178
+ Logger.warn(`${property} was skipped because its type (${typeof value}) is not supported`);
179
+ continue;
180
+ }
181
+ case "undefined":
182
+ case "symbol":
183
+ Logger.warn(`${property} was skipped because its type (${typeof value}) is not supported`);
138
184
  continue;
139
185
  }
186
+ columnData.defaultValue = value === null ? this.dialect.types.null : this.dialect.formatValueToSql(value, dataType);
187
+ columnData.type = this.dialect.types[dataType];
140
188
  columns.push(columnData);
141
189
  }
142
190
  return columns;
@@ -146,60 +194,129 @@ var TableStructureGenerator = class {
146
194
  // src/lib/exceptions/NotAllowedException.ts
147
195
  var NotAllowedException = class extends Error {
148
196
  constructor(entries) {
149
- let message = "Some destructive changes need to be confirmed. In preMigrations() run:";
197
+ let message = "Some destructive changes need to be confirmed. In preMigration() run:\n";
150
198
  for (const entry of entries) {
151
- message += `
152
- migrations.allowMigration(${entry.version}, "${entry.tableName}", "${entry.type}");`;
199
+ message += ` migrations.allowMigration(${entry.version}, ${entry.tableName}, "${entry.type}");
200
+ `;
153
201
  }
154
- super(`${message}
155
-
156
- `);
202
+ super(message);
157
203
  }
158
204
  };
159
205
 
160
- // src/lib/typings/Migrations.ts
206
+ // src/lib/Migrations.ts
161
207
  var Migrations = class {
162
- toVersion = 0;
163
- alwaysAllowed = {};
164
208
  fromVersion = 0;
209
+ toVersion = 0;
210
+ lastUsedVersion = 0;
165
211
  migrationData = {};
212
+ migrationDataForOldTableNames = {};
213
+ alwaysAllowed = {};
166
214
  notAllowedChanges = [];
167
215
  reset(dbInstructions, fromVersion) {
168
216
  this.fromVersion = fromVersion;
169
- this.migrationData = {};
170
217
  this.toVersion = dbInstructions.version;
218
+ this.migrationData = {};
219
+ this.migrationDataForOldTableNames = {};
171
220
  this.alwaysAllowed = dbInstructions.alwaysAllowedMigrations ?? {};
172
221
  this.notAllowedChanges = [];
173
222
  }
174
223
  getEntry(table) {
175
224
  const tableName = this.getTableName(table);
225
+ return this.getEntryFromTableName(tableName);
226
+ }
227
+ getEntryFromTableName(tableName) {
176
228
  if (!this.migrationData.hasOwnProperty(tableName)) {
177
229
  this.migrationData[tableName] = {
178
230
  recreate: false,
179
231
  renamedColumns: [],
180
- allowedMigrations: {}
232
+ allowedMigrations: {},
233
+ usedMigrations: {}
181
234
  };
182
235
  }
183
236
  return this.migrationData[tableName];
184
237
  }
185
238
  getTableName(table) {
186
- return typeof table == "string" ? table : table.name;
187
- }
188
- verifyMigration(tableName, type) {
189
- if (!this.migrationData[tableName]?.allowedMigrations[type] && !this.alwaysAllowed[type])
190
- this.notAllowedChanges.push({ version: this.toVersion, tableName, type });
191
- }
192
- checkIfAllowed(throwIfNotAllowed) {
193
- if (!this.notAllowedChanges.length)
194
- return true;
195
- const exception = new NotAllowedException(this.notAllowedChanges);
196
- if (throwIfNotAllowed)
197
- throw exception;
198
- else {
199
- console.error(exception.message);
239
+ return TableObj.isDbTable(table) ? table.tableName : table.name;
240
+ }
241
+ versionIsRelevant(version) {
242
+ if (version < this.fromVersion || version > this.toVersion)
200
243
  return false;
244
+ if (version < this.lastUsedVersion)
245
+ throw new Error("Migrations in preMigration() have to be ordered by version (beginning with the lowest version).");
246
+ this.lastUsedVersion = version;
247
+ return true;
248
+ }
249
+ verifyRenamingTasks(newTables) {
250
+ for (const tableName in this.migrationData) {
251
+ const migrationEntry = this.migrationData[tableName];
252
+ if (migrationEntry.tableRenaming) {
253
+ if (migrationEntry.tableRenaming.oldName == tableName)
254
+ return new Error(`You set table "${tableName}" to be renamed to itself!`);
255
+ if (migrationEntry.tableRenaming.newName != tableName)
256
+ return new Error(`You set table "${tableName}" to be renamed to a different name than the one in your structure (${migrationEntry.tableRenaming.newName})!`);
257
+ else if (!newTables[tableName])
258
+ return new Error(`You set table "${migrationEntry.tableRenaming}" migrations for "${tableName}". But "${tableName}" does not exist in your structure.`);
259
+ }
260
+ for (const renamingData of migrationEntry.renamedColumns) {
261
+ const oldColumnName = renamingData.oldName;
262
+ const newColumnName = renamingData.newName;
263
+ if (oldColumnName == newColumnName)
264
+ return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to itself!`);
265
+ else if (!newTables[tableName]?.columns.find((column) => column.name == newColumnName))
266
+ return new Error(`You set column ${tableName}.${oldColumnName} to be renamed to ${tableName}.${newColumnName}. But ${tableName}.${newColumnName} does not exist in your structure.`);
267
+ }
268
+ }
269
+ }
270
+ verifyAllowedMigrations() {
271
+ if (this.notAllowedChanges.length)
272
+ return new NotAllowedException(this.notAllowedChanges);
273
+ let errorMsg = "";
274
+ for (const tableName in this.migrationData) {
275
+ const migration = this.migrationData[tableName];
276
+ for (const type in migration.allowedMigrations) {
277
+ if (!migration.usedMigrations[type])
278
+ errorMsg += `Migration "${type}" for ${tableName} was allowed but not needed.
279
+ `;
280
+ }
281
+ }
282
+ if (errorMsg)
283
+ return new Error(errorMsg);
284
+ }
285
+ compareWithAllowedMigration(tableName, type) {
286
+ const migrationEntry = this.migrationData[tableName];
287
+ if (!migrationEntry?.allowedMigrations[type]) {
288
+ if (!this.alwaysAllowed[type])
289
+ this.notAllowedChanges.push({ version: this.toVersion, tableName, type });
290
+ } else if (migrationEntry)
291
+ migrationEntry.usedMigrations[type] = true;
292
+ }
293
+ getMigrationData() {
294
+ return this.migrationData;
295
+ }
296
+ loopRenamedColumns(tableName, callback) {
297
+ const migrationEntry = this.migrationData[tableName];
298
+ for (const renamingData of migrationEntry.renamedColumns) {
299
+ callback(renamingData.oldName, renamingData.newName);
201
300
  }
202
301
  }
302
+ getNewestTableName(tableName) {
303
+ return this.migrationDataForOldTableNames[tableName]?.tableRenaming?.newName ?? tableName;
304
+ }
305
+ getOldTableName(tableName) {
306
+ return this.migrationData[tableName]?.tableRenaming?.oldName ?? tableName;
307
+ }
308
+ getNewestColumnName(tableName, oldColumnName) {
309
+ const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns.oldName == oldColumnName);
310
+ return columnNames ? columnNames.newName : oldColumnName;
311
+ }
312
+ willBeRecreated(tableName) {
313
+ return this.migrationData[tableName]?.recreate;
314
+ }
315
+ internalRecreate(tableName, reason) {
316
+ const entry = this.getEntryFromTableName(tableName);
317
+ entry.recreate = true;
318
+ Logger.log(`Table ${tableName} will be recreated(${reason})!`);
319
+ }
203
320
  allowMigration(version, table, ...allowedMigrations) {
204
321
  if (version < this.fromVersion || version > this.toVersion)
205
322
  return;
@@ -208,52 +325,33 @@ var Migrations = class {
208
325
  entry.allowedMigrations[key] = true;
209
326
  }
210
327
  }
211
- renameTable(version, oldTableName, newTable) {
212
- if (version < this.fromVersion || version > this.toVersion)
328
+ renameTable(version, oldTableName, newTableName) {
329
+ if (!this.versionIsRelevant(version))
213
330
  return;
214
- const entry = this.getEntry(newTable);
215
- entry.recreate = true;
216
- if (!entry.oldTableName)
217
- entry.oldTableName = oldTableName;
331
+ const newTableNameString = this.getTableName(newTableName);
332
+ const entry = this.getEntry(newTableName);
333
+ this.migrationDataForOldTableNames[oldTableName] = entry;
334
+ if (!entry.tableRenaming)
335
+ entry.tableRenaming = { oldName: oldTableName, newName: newTableNameString };
336
+ else
337
+ entry.tableRenaming.newName = newTableNameString;
218
338
  }
219
339
  renameColumn(version, table, oldColumn, newColumn) {
220
- if (version < this.fromVersion || version > this.toVersion)
340
+ if (!this.versionIsRelevant(version))
221
341
  return;
222
342
  const entry = this.getEntry(table);
223
- const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2[entry2.length - 1] == oldColumn);
343
+ const existingColumnEntry = entry.renamedColumns.find((entry2) => entry2.newName == oldColumn);
224
344
  if (existingColumnEntry)
225
- existingColumnEntry.push(newColumn);
345
+ existingColumnEntry.newName = newColumn;
226
346
  else
227
- entry.renamedColumns.push([oldColumn, newColumn]);
228
- }
229
- recreateTableImp(table) {
230
- const entry = this.getEntry(table);
231
- entry.recreate = true;
347
+ entry.renamedColumns.push({ oldName: oldColumn, newName: newColumn });
232
348
  }
233
349
  recreateTable(version, table) {
234
- if (version < this.fromVersion || version > this.toVersion)
350
+ if (!this.versionIsRelevant(version))
235
351
  return;
236
- this.recreateTableImp(table);
237
- }
238
- getMigrationData() {
239
- return this.migrationData;
240
- }
241
- loopRenamedColumns(tableName, callback) {
242
- const migrationEntry = this.migrationData[tableName];
243
- for (const renamingArray of migrationEntry.renamedColumns) {
244
- if (renamingArray.length <= 1)
245
- continue;
246
- const oldColumnName = renamingArray[0];
247
- const newColumnName = renamingArray[renamingArray.length - 1];
248
- callback(oldColumnName, newColumnName);
249
- }
250
- }
251
- getUpdatedColumnName(tableName, oldColumnName) {
252
- const columnNames = this.migrationData[tableName]?.renamedColumns.find((columns) => columns[0] == oldColumnName);
253
- return columnNames ? columnNames[columnNames.length - 1] : oldColumnName;
254
- }
255
- willBeRecreated(tableName) {
256
- return this.migrationData[tableName]?.recreate;
352
+ const entry = this.getEntry(table);
353
+ entry.recreate = true;
354
+ Logger.log(`Table ${this.getTableName(table)} will be recreated!`);
257
355
  }
258
356
  };
259
357
 
@@ -261,6 +359,10 @@ var Migrations = class {
261
359
  var MigrationManager = class {
262
360
  migrations = new Migrations();
263
361
  newTables = {};
362
+ /**
363
+ * Table names that currently exist in the database
364
+ * Note: If tables are set to be renamed, then names in this array will be corrected to their future name.
365
+ */
264
366
  existingTables = [];
265
367
  dialect;
266
368
  constructor(dialect) {
@@ -318,8 +420,9 @@ ${preSQL.down}`;
318
420
  changes.down += initialChanges.down;
319
421
  } else {
320
422
  Logger.log(`Creating migration SQL from version ${fromVersion} to ${toVersion}`);
321
- this.existingTables = await this.dialect.getTableNames();
423
+ this.existingTables = (await this.dialect.getTableNames()).map((oldTableName) => this.migrations.getNewestTableName(oldTableName));
322
424
  [
425
+ this.renameTables(),
323
426
  await this.createAndDropTables(),
324
427
  await this.migrateForeignKeys(),
325
428
  await this.migrateColumns(),
@@ -348,8 +451,18 @@ ${foreignKeyOnQuery}`;
348
451
  changes.down += `
349
452
 
350
453
  ${foreignKeyOnQuery}`;
351
- if (!this.migrations.checkIfAllowed(dbInstructions.throwIfNotAllowed)) {
352
- return null;
454
+ const exception = this.migrations.verifyRenamingTasks(this.newTables) ?? this.migrations.verifyAllowedMigrations();
455
+ if (exception) {
456
+ Logger.debug(`
457
+ Canceled changes:
458
+ ${changes.up}
459
+ `);
460
+ if (dbInstructions.throwIfNotAllowed)
461
+ throw exception;
462
+ else {
463
+ console.error(exception.message);
464
+ return null;
465
+ }
353
466
  }
354
467
  return {
355
468
  fromVersion,
@@ -357,6 +470,22 @@ ${foreignKeyOnQuery}`;
357
470
  changes
358
471
  };
359
472
  }
473
+ renameTables() {
474
+ const changes = {
475
+ up: "\n\n-- Rename tables\n",
476
+ down: "\n\n-- Rename tables\n"
477
+ };
478
+ const data = this.migrations.getMigrationData();
479
+ for (const tableName in data) {
480
+ const migrationEntry = data[tableName];
481
+ if (migrationEntry.tableRenaming) {
482
+ Logger.log(`Table "${migrationEntry.tableRenaming.oldName}" will be renamed to "${tableName}"!`);
483
+ changes.up += this.dialect.renameTable(migrationEntry.tableRenaming.oldName, tableName);
484
+ changes.down += this.dialect.renameTable(tableName, migrationEntry.tableRenaming.oldName);
485
+ }
486
+ }
487
+ return changes;
488
+ }
360
489
  /**
361
490
  * Generates SQL change statements for creating and dropping tables based on the current table structure
362
491
  * and existing table configurations in the database. The `up` changes include SQL statements for creating
@@ -368,8 +497,8 @@ ${foreignKeyOnQuery}`;
368
497
  */
369
498
  async createAndDropTables() {
370
499
  let changes = {
371
- up: "\n\n-- Create tables\n",
372
- down: "\n\n-- Create tables\n"
500
+ up: "\n\n-- Create and drop tables\n",
501
+ down: "\n\n-- Create and drop tables\n"
373
502
  };
374
503
  for (const tableName in this.newTables) {
375
504
  if (this.tableDoesNotExists(tableName)) {
@@ -382,12 +511,13 @@ ${foreignKeyOnQuery}`;
382
511
  }
383
512
  for (const tableName of this.existingTables) {
384
513
  if (!this.newTables[tableName]) {
385
- this.migrations.verifyMigration(tableName, "dropTable");
514
+ this.migrations.compareWithAllowedMigration(tableName, "dropTable");
386
515
  if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
387
- this.migrations.verifyMigration(tableName, "continueWithoutRollback");
516
+ this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
517
+ const dbTableName = this.migrations.getOldTableName(tableName);
388
518
  changes.up += `${this.dialect.dropTable(tableName)}
389
519
  `;
390
- 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";
520
+ 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";
391
521
  }
392
522
  }
393
523
  return changes;
@@ -438,10 +568,10 @@ ${foreignKeyOnQuery}`;
438
568
  const migrationEntry = migrationData[tableName];
439
569
  if (!migrationEntry.recreate)
440
570
  continue;
441
- this.migrations.verifyMigration(tableName, "recreateTable");
571
+ this.migrations.compareWithAllowedMigration(tableName, "recreateTable");
442
572
  if (!this.dialect.canInspectForeignKeys || !this.dialect.canInspectPrimaryKey)
443
- this.migrations.verifyMigration(tableName, "continueWithoutRollback");
444
- const oldColumnList = await this.dialect.getColumnInformation(tableName);
573
+ this.migrations.compareWithAllowedMigration(tableName, "continueWithoutRollback");
574
+ const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
445
575
  const newColumnList = this.newTables[tableName].columns;
446
576
  const moveableColumns = newColumnList.filter((entry) => oldColumnList.find((oldEntry) => oldEntry.name == entry.name) != void 0).map((entry) => entry.name);
447
577
  const backupTableName = `${tableName}__backup`;
@@ -451,8 +581,9 @@ ${foreignKeyOnQuery}`;
451
581
  );
452
582
  const moveDataQuery = insertQuery + "\n" + this.dialect.dropTable(tableName) + "\n" + this.dialect.renameTable(backupTableName, tableName);
453
583
  const structure = this.newTables[tableName];
454
- changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery;
455
- 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";
584
+ changes.up += this.createTableSql(backupTableName, structure.columns, structure.foreignKeys) + "\n" + moveDataQuery + "\n";
585
+ const dbTableName = this.migrations.getOldTableName(tableName);
586
+ 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";
456
587
  }
457
588
  return changes;
458
589
  }
@@ -460,8 +591,6 @@ ${foreignKeyOnQuery}`;
460
591
  * Migrates foreign key constraints for all defined tables.
461
592
  */
462
593
  async migrateForeignKeys() {
463
- if (!this.dialect.canAlterForeignKeys)
464
- return { up: "", down: "" };
465
594
  let changes = {
466
595
  up: "\n\n-- Foreign keys\n",
467
596
  down: "\n\n-- Foreign keys\n"
@@ -471,13 +600,13 @@ ${foreignKeyOnQuery}`;
471
600
  continue;
472
601
  const structure = this.newTables[tableName];
473
602
  const newForeignKeys = structure.foreignKeys ?? [];
474
- const oldForeignKeys = await this.dialect.getForeignKeys(tableName);
603
+ const oldForeignKeys = await this.dialect.getForeignKeys(this.migrations.getOldTableName(tableName));
475
604
  let checkForNewForeignKeys = true;
476
605
  for (const oldForeignKey of oldForeignKeys) {
477
606
  const newForeignKey = newForeignKeys.find((entry) => entry.fromColumn == oldForeignKey.fromColumn);
478
607
  if (!newForeignKey) {
479
- Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was removed!`);
480
- this.migrations.verifyMigration(tableName, "removeForeignKey");
608
+ Logger.log(`Foreign key ${oldForeignKey.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be removed!`);
609
+ this.migrations.compareWithAllowedMigration(tableName, "removeForeignKey");
481
610
  if (this.dialect.canAlterForeignKeys) {
482
611
  changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn);
483
612
  changes.down += this.dialect.addForeignKey(
@@ -485,13 +614,13 @@ ${foreignKeyOnQuery}`;
485
614
  this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
486
615
  );
487
616
  } else {
488
- this.migrations.recreateTableImp(structure.table);
617
+ this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
489
618
  checkForNewForeignKeys = false;
490
619
  }
491
620
  } else {
492
621
  if (!newForeignKey || oldForeignKey.toColumn != newForeignKey.toColumn || (oldForeignKey.onUpdate ?? "NO ACTION") != (newForeignKey.onUpdate ?? "NO ACTION") || (oldForeignKey.onDelete ?? "NO ACTION") != (newForeignKey.onDelete ?? "NO ACTION")) {
493
- Logger.log(`Foreign key ${oldForeignKey.toTable}.${oldForeignKey.toColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} was changed!`);
494
- this.migrations.verifyMigration(tableName, "alterForeignKey");
622
+ Logger.log(`Foreign key ${oldForeignKey.fromTable}.${oldForeignKey.fromColumn} to ${oldForeignKey.toTable}.${oldForeignKey.toColumn} will be changed!`);
623
+ this.migrations.compareWithAllowedMigration(tableName, "alterForeignKey");
495
624
  if (this.dialect.canAlterForeignKeys) {
496
625
  changes.up += this.dialect.removeForeignKey(oldForeignKey.fromTable, oldForeignKey.fromColumn) + this.dialect.addForeignKey(
497
626
  newForeignKey.fromTable,
@@ -502,7 +631,7 @@ ${foreignKeyOnQuery}`;
502
631
  this.dialect.foreignKey(oldForeignKey.fromColumn, oldForeignKey.toTable, oldForeignKey.toColumn, oldForeignKey.onUpdate, oldForeignKey.onDelete)
503
632
  ) + this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
504
633
  } else {
505
- this.migrations.recreateTableImp(structure.table);
634
+ this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
506
635
  checkForNewForeignKeys = false;
507
636
  }
508
637
  }
@@ -513,7 +642,7 @@ ${foreignKeyOnQuery}`;
513
642
  const oldForeignKey = oldForeignKeys.find((entry) => entry.fromColumn == newForeignKey.fromColumn);
514
643
  if (oldForeignKey)
515
644
  continue;
516
- Logger.log(`Foreign key ${newForeignKey.toTable}.${newForeignKey.toColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} is new!`);
645
+ Logger.log(`Foreign key ${newForeignKey.fromTable}.${newForeignKey.fromColumn} to ${newForeignKey.toTable}.${newForeignKey.toColumn} will be added!`);
517
646
  if (this.dialect.canAlterForeignKeys) {
518
647
  changes.up += this.dialect.addForeignKey(
519
648
  newForeignKey.toTable,
@@ -521,7 +650,7 @@ ${foreignKeyOnQuery}`;
521
650
  );
522
651
  changes.down += this.dialect.removeForeignKey(newForeignKey.fromTable, newForeignKey.fromColumn);
523
652
  } else {
524
- this.migrations.recreateTableImp(structure.table);
653
+ this.migrations.internalRecreate(structure.table, "Dialect does not support altering foreign keys");
525
654
  break;
526
655
  }
527
656
  }
@@ -542,13 +671,13 @@ ${foreignKeyOnQuery}`;
542
671
  if (this.migrations.willBeRecreated(tableName) || this.tableDoesNotExists(tableName))
543
672
  continue;
544
673
  const newTableDefinition = this.newTables[tableName];
545
- const oldColumnList = await this.dialect.getColumnInformation(tableName);
674
+ const oldColumnList = await this.dialect.getColumnInformation(this.migrations.getOldTableName(tableName));
546
675
  const oldPrimaryKey = this.dialect.canInspectPrimaryKey ? this.getPrimaryKeyColumn(oldColumnList) : false;
547
676
  const newColumnList = newTableDefinition.columns;
548
677
  const newPrimaryKey = newTableDefinition.primaryKey;
549
678
  if (this.dialect.canInspectPrimaryKey && oldPrimaryKey != newPrimaryKey) {
550
- Logger.log(`Primary key in ${tableName} was changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
551
- this.migrations.verifyMigration(tableName, "alterPrimaryKey");
679
+ Logger.log(`Primary key in ${tableName} will be changed from ${oldPrimaryKey} to ${newPrimaryKey}!`);
680
+ this.migrations.compareWithAllowedMigration(tableName, "alterPrimaryKey");
552
681
  if (this.dialect.canAlterPrimaryKey) {
553
682
  if (oldPrimaryKey) {
554
683
  changes.up += this.dialect.removePrimaryKey(tableName, oldPrimaryKey);
@@ -559,26 +688,26 @@ ${foreignKeyOnQuery}`;
559
688
  changes.down += this.dialect.removePrimaryKey(tableName, newPrimaryKey.toString());
560
689
  }
561
690
  } else
562
- this.migrations.recreateTableImp(newTableDefinition.table);
691
+ this.migrations.internalRecreate(newTableDefinition.table, "Dialect does not support altering primary keys");
563
692
  continue;
564
693
  }
565
694
  for (const newColumn of newColumnList) {
566
695
  const oldColumn = oldColumnList.find(
567
- (entry) => this.migrations.getUpdatedColumnName(tableName, entry.name) == newColumn.name
696
+ (entry) => this.migrations.getNewestColumnName(tableName, entry.name) == newColumn.name
568
697
  );
569
698
  if (oldColumn == void 0) {
570
699
  changes.up += this.dialect.createColumn(tableName, this.dialect.columnDefinition(newColumn.name, newColumn.type, newColumn.defaultValue, newColumn.isPrimaryKey)) + "\n";
571
700
  changes.down += this.dialect.dropColumn(tableName, newColumn.name) + "\n";
572
701
  } else if (newColumn.type != oldColumn.type || newColumn.defaultValue != oldColumn.defaultValue) {
573
- this.migrations.recreateTableImp(newTableDefinition.table);
702
+ this.migrations.internalRecreate(newTableDefinition.table, "Table structure was changed");
574
703
  }
575
704
  }
576
705
  for (const oldColumn of oldColumnList) {
577
706
  const newColumn = newColumnList.find(
578
- (entry) => entry.name == this.migrations.getUpdatedColumnName(tableName, oldColumn.name)
707
+ (entry) => entry.name == this.migrations.getNewestColumnName(tableName, oldColumn.name)
579
708
  );
580
709
  if (newColumn == void 0) {
581
- this.migrations.verifyMigration(tableName, "dropColumn");
710
+ this.migrations.compareWithAllowedMigration(tableName, "dropColumn");
582
711
  changes.up += this.dialect.dropColumn(tableName, oldColumn.name) + "\n";
583
712
  changes.down += this.dialect.createColumn(tableName, this.dialect.columnDefinition(oldColumn.name, oldColumn.type, oldColumn.defaultValue, oldColumn.isPrimaryKey)) + "\n";
584
713
  }
@@ -612,6 +741,7 @@ ${foreignKeyOnQuery}`;
612
741
  if (migrationData[tableName].recreate)
613
742
  continue;
614
743
  this.migrations.loopRenamedColumns(tableName, (oldColumnName, newColumnName) => {
744
+ Logger.log(`Column ${tableName}.${oldColumnName} will be renamed to ${tableName}.${newColumnName}!`);
615
745
  changes.up += this.dialect.renameColumn(tableName, oldColumnName, newColumnName);
616
746
  changes.down += this.dialect.renameColumn(tableName, newColumnName, oldColumnName);
617
747
  });
@@ -678,24 +808,37 @@ ${changes.down}`, { encoding: "utf-8" });
678
808
  }
679
809
  };
680
810
 
681
- // src/lib/tableInfo/decorators/DbTable.ts
811
+ // src/lib/tableInfo/decorators/TableClass.ts
682
812
  var import_polyfill_symbol_metadata = require("polyfill-symbol-metadata");
683
- function DbTable(tableName, primaryKey) {
813
+ function TableClass(tableName, primaryKey) {
684
814
  return (table, context) => {
685
- Object.defineProperty(table, "name", { value: tableName, writable: false });
686
815
  const tableInfo = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
687
816
  tableInfo.primaryKey = primaryKey?.toString();
688
817
  if (tableInfo.foreignKeys) {
689
818
  for (const foreignKey of tableInfo.foreignKeys) {
690
819
  foreignKey.fromTable = tableName;
820
+ if (foreignKey.toTable == table.name)
821
+ foreignKey.toTable = tableName;
691
822
  }
692
823
  }
824
+ Object.defineProperty(table, "name", { value: tableName, writable: false });
693
825
  };
694
826
  }
695
827
 
696
- // src/lib/tableInfo/decorators/ForeignKey.ts
828
+ // src/lib/tableInfo/decorators/DataType.ts
697
829
  var import_polyfill_symbol_metadata2 = require("polyfill-symbol-metadata");
698
- function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
830
+ function DataType(type) {
831
+ return (table, context) => {
832
+ const metadata = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
833
+ if (!metadata.dataTypes)
834
+ metadata.dataTypes = {};
835
+ metadata.dataTypes[context.name ?? context] = type;
836
+ };
837
+ }
838
+
839
+ // src/lib/tableInfo/decorators/ForeignKey.ts
840
+ var import_polyfill_symbol_metadata3 = require("polyfill-symbol-metadata");
841
+ function ForeignKey(toTable, toColumn, options) {
699
842
  return (table, context) => {
700
843
  const metadata = context?.metadata ? getTableInfoFromMetadata(context.metadata) : getTableInfo(table);
701
844
  if (!metadata.foreignKeys)
@@ -706,8 +849,8 @@ function ForeignKey(toTable, toColumn, onDelete, onUpdate) {
706
849
  //to stay compatible with old decorator structure (see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)
707
850
  toTable: toTable.name,
708
851
  toColumn: toColumn.toString(),
709
- onDelete,
710
- onUpdate
852
+ onDelete: options?.onDelete,
853
+ onUpdate: options?.onUpdate
711
854
  });
712
855
  };
713
856
  }
@@ -720,32 +863,108 @@ var DefaultSql = class {
720
863
  canAlterPrimaryKey = false;
721
864
  canInspectForeignKeys = false;
722
865
  canInspectPrimaryKey = false;
723
- typeString = "TEXT";
724
- typeInt = "INTEGER";
725
- typeBigInt = "BIGINT";
726
- typeBoolean = "BOOLEAN";
727
- typeNull = "NULL";
866
+ types = {
867
+ string: "TEXT",
868
+ number: "INTEGER",
869
+ bigint: "BIGINT",
870
+ boolean: "BOOLEAN",
871
+ date: "DATE",
872
+ time: "TIME",
873
+ dateTime: "DATETIME",
874
+ null: "NULL"
875
+ };
728
876
  constructor(db) {
729
877
  this.db = db;
730
878
  }
731
- formatValueToSql(value) {
732
- return value.toString();
879
+ /**
880
+ * Formats a given value to a SQL-equivalent string based on the provided data type.
881
+ *
882
+ * @param value - The value to be formatted.
883
+ * @param type - The data type of the value. Either determined through typeof or {@link TableInfo} setting
884
+ * @return A string representation of the value formatted for SQL.
885
+ */
886
+ formatValueToSql(value, type) {
887
+ function toDate(value2) {
888
+ return value2 instanceof Date ? value2 : new Date(value2);
889
+ }
890
+ switch (type) {
891
+ case "string":
892
+ return `"${value}"`;
893
+ case "time":
894
+ return toDate(value).toISOString().slice(11, 19);
895
+ case "date":
896
+ return toDate(value).toISOString().slice(0, 10);
897
+ case "dateTime":
898
+ return toDate(value).toISOString().slice(0, 19).replace("T", " ");
899
+ default:
900
+ return value.toString();
901
+ }
733
902
  }
903
+ /**
904
+ * Query to enable / disable foreign keys in the database.
905
+ * Is dialect specific.
906
+ *
907
+ * @param enabled - A boolean indicating whether to enable or disable foreign key constraints.
908
+ * @return The query
909
+ */
734
910
  changeForeignKeysState(enabled) {
735
911
  return "";
736
912
  }
913
+ /**
914
+ * Query to add a foreign key to a specified table.
915
+ * Is only used if {@link canAlterForeignKeys} is true.
916
+ *
917
+ * @param fromTableName - The name of the table to which the foreign key will be added.
918
+ * @param foreignKey - The foreign key definition to be added, see {@link foreignKey()}.
919
+ * @return The SQL statement that adds the foreign key to the specified table.
920
+ */
737
921
  addForeignKey(fromTableName, foreignKey) {
738
922
  throw new Error("Adding foreign keys is not supported!");
739
923
  }
924
+ /**
925
+ * Query to remove a foreign key from a specified table.
926
+ * Is only used if {@link canAlterForeignKeys} is true.
927
+ *
928
+ * @param tableName - The name of the table to which the foreign key will be added.
929
+ * @param foreignKeyName - The name of the foreign key definition.
930
+ * @return The SQL statement that removes the foreign key from the specified table.
931
+ */
740
932
  removeForeignKey(tableName, foreignKeyName) {
741
933
  throw new Error("Foreign key removal is not supported!");
742
934
  }
935
+ /**
936
+ * Query to add a primary key to the specified table.
937
+ * Is only used if {@link canAlterPrimaryKey} is true.
938
+ *
939
+ * @param tableName - The name of the table to which the primary key will be added.
940
+ * @param columnName - The name of the column that will be set as the primary key.
941
+ * @return A SQL statement to add a primary key to the specified table and column.
942
+ */
743
943
  addPrimaryKey(tableName, columnName) {
744
944
  throw new Error("Adding foreign keys is not supported!");
745
945
  }
946
+ /**
947
+ * Query to remove a primary key from the specified table.
948
+ * Is only used if {@link canAlterPrimaryKey} is true.
949
+ *
950
+ * @param tableName - The name of the table to which the primary key will be added.
951
+ * @param columnName - The name of the column that will be set as the primary key.
952
+ * @return A SQL statement to remove a primary key from the specified table and column.
953
+ */
746
954
  removePrimaryKey(tableName, columnName) {
747
955
  throw new Error("Foreign key removal is not supported!");
748
956
  }
957
+ /**
958
+ * Constructs a SQL FOREIGN KEY constraint clause.
959
+ * Used in combination with {@link addForeignKey} or {@link createTable}
960
+ *
961
+ * @param column - The name of the column in the current table to apply the foreign key constraint.
962
+ * @param foreignTable - The name of the referenced table.
963
+ * @param foreignColumn - The name of the referenced column in the foreign table.
964
+ * @param onUpdate - Optional action to specify the behavior on update (e.g., CASCADE, SET NULL).
965
+ * @param onDelete - Optional action to specify the behavior on delete (e.g., CASCADE, SET NULL).
966
+ * @return The SQL string representing the foreign key constraint.
967
+ */
749
968
  foreignKey(column, foreignTable, foreignColumn, onUpdate, onDelete) {
750
969
  let query = `FOREIGN KEY (${column}) REFERENCES ${foreignTable} (${foreignColumn})`;
751
970
  if (onUpdate)
@@ -795,7 +1014,7 @@ var DefaultSql = class {
795
1014
  }
796
1015
  migrationTableQuery() {
797
1016
  return this.createTable(MIGRATION_DATA_TABLE_NAME, [
798
- this.columnDefinition("version", this.typeInt, "0", false)
1017
+ this.columnDefinition("version", this.types.number, "0", false)
799
1018
  ]);
800
1019
  }
801
1020
  async getForeignKeys(tableName) {
@@ -824,8 +1043,16 @@ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SE
824
1043
 
825
1044
  // src/lib/dialects/SqliteDialect.ts
826
1045
  var SqliteDialect = class extends DefaultSql {
827
- typeBoolean = "INTEGER";
828
- typeBigInt = "INTEGER";
1046
+ types = {
1047
+ string: "TEXT",
1048
+ number: "INTEGER",
1049
+ bigint: "INTEGER",
1050
+ boolean: "INTEGER",
1051
+ date: "DATE",
1052
+ time: "TIME",
1053
+ dateTime: "DATETIME",
1054
+ null: "NULL"
1055
+ };
829
1056
  canAlterForeignKeys = false;
830
1057
  canAlterPrimaryKey = false;
831
1058
  canInspectForeignKeys = true;
@@ -847,12 +1074,19 @@ var SqliteDialect = class extends DefaultSql {
847
1074
  };
848
1075
  });
849
1076
  }
850
- formatValueToSql(value) {
851
- switch (typeof value) {
1077
+ formatValueToSql(value, type) {
1078
+ function toDate(value2) {
1079
+ return value2 instanceof Date ? value2 : new Date(value2);
1080
+ }
1081
+ switch (type) {
852
1082
  case "boolean":
853
1083
  return value ? "1" : "0";
1084
+ case "date":
1085
+ case "time":
1086
+ case "dateTime":
1087
+ return toDate(value).getTime().toString();
854
1088
  default:
855
- return value.toString();
1089
+ return super.formatValueToSql(value, type);
856
1090
  }
857
1091
  }
858
1092
  async getForeignKeys(tableName) {
@@ -1090,8 +1324,10 @@ async function rollback(db, dbInstructions, toVersion) {
1090
1324
  }
1091
1325
  // Annotate the CommonJS export names for ESM import in node:
1092
1326
  0 && (module.exports = {
1093
- DbTable,
1327
+ DataType,
1094
1328
  ForeignKey,
1329
+ TableClass,
1330
+ TableObj,
1095
1331
  prepareAndRunMigration,
1096
1332
  prepareMigration,
1097
1333
  rollback,