sqlmorpheus 0.1.4 → 0.1.6

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.d.mts CHANGED
@@ -121,6 +121,7 @@ declare function ForeignKey<TOther extends BackendTable>(toTable: Class<TOther>,
121
121
  declare function prepareMigration(db: DatabaseAccess, instructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
122
122
  declare function runPreparedMigrations(db: DatabaseAccess, instructions: DatabaseInstructions): Promise<void>;
123
123
  declare function prepareAndRunMigration(db: DatabaseAccess, instructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
124
+ declare function runMigrationWithoutHistory(db: DatabaseAccess, instructions: DatabaseInstructions): Promise<void>;
124
125
  declare function rollback(db: DatabaseAccess, instructions: DatabaseInstructions, toVersion: number): Promise<void>;
125
126
 
126
- export { type DatabaseInstructions, DbTable, ForeignKey, prepareAndRunMigration, prepareMigration, rollback, runPreparedMigrations };
127
+ export { type DatabaseInstructions, DbTable, ForeignKey, prepareAndRunMigration, prepareMigration, rollback, runMigrationWithoutHistory, runPreparedMigrations };
package/dist/index.d.ts CHANGED
@@ -121,6 +121,7 @@ declare function ForeignKey<TOther extends BackendTable>(toTable: Class<TOther>,
121
121
  declare function prepareMigration(db: DatabaseAccess, instructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
122
122
  declare function runPreparedMigrations(db: DatabaseAccess, instructions: DatabaseInstructions): Promise<void>;
123
123
  declare function prepareAndRunMigration(db: DatabaseAccess, instructions: DatabaseInstructions, overwriteExisting?: boolean): Promise<void>;
124
+ declare function runMigrationWithoutHistory(db: DatabaseAccess, instructions: DatabaseInstructions): Promise<void>;
124
125
  declare function rollback(db: DatabaseAccess, instructions: DatabaseInstructions, toVersion: number): Promise<void>;
125
126
 
126
- export { type DatabaseInstructions, DbTable, ForeignKey, prepareAndRunMigration, prepareMigration, rollback, runPreparedMigrations };
127
+ export { type DatabaseInstructions, DbTable, ForeignKey, prepareAndRunMigration, prepareMigration, rollback, runMigrationWithoutHistory, runPreparedMigrations };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  prepareAndRunMigration: () => prepareAndRunMigration,
26
26
  prepareMigration: () => prepareMigration,
27
27
  rollback: () => rollback,
28
+ runMigrationWithoutHistory: () => runMigrationWithoutHistory,
28
29
  runPreparedMigrations: () => runPreparedMigrations
29
30
  });
30
31
  module.exports = __toCommonJS(index_exports);
@@ -114,7 +115,7 @@ var TableStructureGenerator = class {
114
115
  columnData.defaultValue = value === null ? this.dialect.typeNull : `"${this.dialect.formatValueToSql(value)}"`;
115
116
  break;
116
117
  case "number":
117
- columnData.type = this.dialect.typeNumber;
118
+ columnData.type = this.dialect.typeInt;
118
119
  columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
119
120
  break;
120
121
  case "boolean":
@@ -253,18 +254,15 @@ var MigrationHistoryManager = class {
253
254
  (0, import_node_fs.mkdirSync)(this.configPath, { recursive: true });
254
255
  (0, import_node_fs.mkdirSync)(this.migrationsPath, { recursive: true });
255
256
  }
256
- getLastHistoryVersion() {
257
- if (this.lastVersion)
258
- return this.lastVersion;
259
- const path = `${this.configPath}/last_version.txt`;
260
- this.lastVersion = (0, import_node_fs.existsSync)(path) ? parseInt((0, import_fs.readFileSync)(`${this.configPath}/last_version.txt`, { encoding: "utf-8" })) ?? 0 : 0;
261
- return this.lastVersion;
262
- }
263
- setLastHistoryVersion(version) {
264
- (0, import_node_fs.writeFileSync)(`${this.configPath}/last_version.txt`, version.toString(), { encoding: "utf-8" });
265
- }
266
- createMigrationHistory(toVersion, changes, overwriteExisting) {
267
- const fromVersion = this.getLastHistoryVersion();
257
+ /**
258
+ * Creates migration history files for upgrading and downgrading database schemas.
259
+ *
260
+ * @param fromVersion - The current version number of the database.
261
+ * @param toVersion - The target version number for the migration.
262
+ * @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
263
+ * @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
264
+ */
265
+ createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
268
266
  const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
269
267
  const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
270
268
  if ((0, import_node_fs.existsSync)(upPath) && !overwriteExisting)
@@ -279,22 +277,36 @@ ${changes.up}`, { encoding: "utf-8" });
279
277
  -- To: Version ${fromVersion}
280
278
  ${changes.down}`, { encoding: "utf-8" });
281
279
  }
280
+ /**
281
+ * Retrieves the SQL migration script for upgrading to the specified version.
282
+ * If no version is provided, it retrieves the migration script for the latest version in the history.
283
+ *
284
+ * @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
285
+ * @return The content of the SQL migration script as a string.
286
+ */
282
287
  getUpMigration(toVersion) {
283
- return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion ?? this.getLastHistoryVersion()}.sql`, { encoding: "utf-8" });
288
+ return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
284
289
  }
290
+ /**
291
+ * Retrieves the SQL migration script for downgrading to the specified version.
292
+ *
293
+ * @param version - The version number of the migration to retrieve.
294
+ * @return The SQL script for the down migration.
295
+ */
285
296
  getDownMigration(version) {
286
297
  return (0, import_fs.readFileSync)(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
287
298
  }
288
299
  };
289
300
 
290
301
  // src/lib/dialects/DefaultSql.ts
302
+ var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
291
303
  var DefaultSql = class {
292
304
  canAlterForeignKeys = false;
293
305
  canAlterPrimaryKey = false;
294
306
  canInspectForeignKeys = false;
295
307
  canInspectPrimaryKey = false;
296
308
  typeString = "TEXT";
297
- typeNumber = "INTEGER";
309
+ typeInt = "INTEGER";
298
310
  typeBoolean = "BOOLEAN";
299
311
  typeNull = "NULL";
300
312
  formatValueToSql(value) {
@@ -337,8 +349,8 @@ var DefaultSql = class {
337
349
  dropTable(tableName) {
338
350
  return `DROP TABLE IF EXISTS ${tableName};`;
339
351
  }
340
- columnDefinition(tableName, type, defaultValue, isPrimaryKey) {
341
- const query = `${tableName} ${type} DEFAULT ${defaultValue}`;
352
+ columnDefinition(columnName, type, defaultValue, isPrimaryKey) {
353
+ const query = `${columnName} ${type} DEFAULT ${defaultValue}`;
342
354
  return isPrimaryKey ? `${query} PRIMARY KEY` : query;
343
355
  }
344
356
  createColumn(columnTable, entry) {
@@ -365,6 +377,30 @@ var DefaultSql = class {
365
377
  insertValues(keys, valueString) {
366
378
  return `(${keys}) ${valueString ?? `VALUES (${keys.map(() => "?").join(",")})`}`;
367
379
  }
380
+ migrationTableQuery() {
381
+ return this.createTable(MIGRATION_DATA_TABLE_NAME, [
382
+ this.columnDefinition("version", this.typeInt, "0", false)
383
+ ]);
384
+ }
385
+ async createMigrationTableIfNeeded(db) {
386
+ await db.runMultipleWriteStatements(this.migrationTableQuery());
387
+ }
388
+ //TODO: untested
389
+ async getVersion(db) {
390
+ await this.createMigrationTableIfNeeded(db);
391
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
392
+ const data = await db.runGetStatement(query);
393
+ return data.length ? data[0].version : 0;
394
+ }
395
+ //TODO: untested
396
+ async setVersion(db, newVersion) {
397
+ const query = `${this.migrationTableQuery()};
398
+ INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
399
+ UNION ALL
400
+ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
401
+ `;
402
+ await db.runMultipleWriteStatements(query);
403
+ }
368
404
  };
369
405
 
370
406
  // src/lib/dialects/SqliteDialect.ts
@@ -412,6 +448,13 @@ var SqliteDialect = class extends DefaultSql {
412
448
  return value.toString();
413
449
  }
414
450
  }
451
+ async getVersion(db) {
452
+ const output = await db.runGetStatement(`PRAGMA user_version;`);
453
+ return output[0].user_version;
454
+ }
455
+ async setVersion(db, newVersion) {
456
+ await db.runMultipleWriteStatements(`PRAGMA user_version = ${newVersion};`);
457
+ }
415
458
  };
416
459
 
417
460
  // src/lib/dialects/PostgresDialect.ts
@@ -550,6 +593,22 @@ var MySqlDialect = class extends DefaultSql {
550
593
  }
551
594
  };
552
595
 
596
+ // src/lib/getDialect.ts
597
+ function getDialect(dbInstructions) {
598
+ switch (dbInstructions.dialect) {
599
+ case "Sqlite":
600
+ return new SqliteDialect();
601
+ case "Postgres":
602
+ return new PostgresDialect();
603
+ case "MsSql":
604
+ return new MsSqlDialect();
605
+ case "MySql":
606
+ return new MySqlDialect();
607
+ default:
608
+ throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
609
+ }
610
+ }
611
+
553
612
  // src/lib/MigrationManager.ts
554
613
  var MigrationManager = class {
555
614
  migrations = new Migrations();
@@ -560,31 +619,27 @@ var MigrationManager = class {
560
619
  migrationHistoryManager;
561
620
  dialect;
562
621
  constructor(db, dbInstructions) {
563
- switch (dbInstructions.dialect) {
564
- case "Sqlite":
565
- this.dialect = new SqliteDialect();
566
- break;
567
- case "Postgres":
568
- this.dialect = new PostgresDialect();
569
- break;
570
- case "MsSql":
571
- this.dialect = new MsSqlDialect();
572
- break;
573
- case "MySql":
574
- this.dialect = new MySqlDialect();
575
- break;
576
- default:
577
- throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
578
- }
622
+ this.dialect = getDialect(dbInstructions);
579
623
  this.db = db;
580
624
  this.dbInstructions = dbInstructions;
581
625
  this.tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
582
626
  this.migrationHistoryManager = new MigrationHistoryManager(this.dbInstructions.configPath);
583
627
  }
628
+ /**
629
+ * Generates the SQL changes required to migrate a database from its current version
630
+ * to the target version defined in the database instructions. Handles the creation
631
+ * and deletion of tables, migration of foreign keys and columns, renaming of columns,
632
+ * and recreating tables, along with executing any pre- and post-migration custom logic.
633
+ *
634
+ * @return A promise that resolves to an object containing the SQL
635
+ * changes for the migration (`up` and `down` changes) or null if no migration is required.
636
+ * Throws an error in scenarios such as attempting to migrate to a lower version
637
+ * or an invalid starting version.
638
+ */
584
639
  async getMigrateSql() {
585
640
  if (this.dbInstructions.version <= 0)
586
641
  throw new Error("Cannot migrate to version 0 or lower");
587
- const fromVersion = this.migrationHistoryManager.getLastHistoryVersion();
642
+ const fromVersion = await this.dialect.getVersion(this.db);
588
643
  if (fromVersion == 0) {
589
644
  Logger.log(`Creating initial migration to version ${this.dbInstructions.version}`);
590
645
  return this.createAndDropTables();
@@ -649,12 +704,29 @@ ${foreignKeyOnQuery}`;
649
704
  ${foreignKeyOnQuery}`;
650
705
  return changes;
651
706
  }
707
+ /**
708
+ * Prepares the migration by generating the SQL changes and creating a migration history entry.
709
+ * If no changes are needed, the method exits early.
710
+ *
711
+ * @param overwriteExisting - Optional flag indicating if overwriting existing changes is allowed.
712
+ * @return - A promise that resolves once the migration preparation is complete.
713
+ */
652
714
  async prepareMigration(overwriteExisting) {
653
715
  const changes = await this.getMigrateSql();
654
716
  if (!changes)
655
717
  return;
656
- this.migrationHistoryManager.createMigrationHistory(this.dbInstructions.version, changes, overwriteExisting);
718
+ const fromVersion = await this.dialect.getVersion(this.db);
719
+ this.migrationHistoryManager.createMigrationHistory(fromVersion, this.dbInstructions.version, changes, overwriteExisting);
657
720
  }
721
+ /**
722
+ * Generates SQL change statements for creating and dropping tables based on the current table structure
723
+ * and existing table configurations in the database. The `up` changes include SQL statements for creating
724
+ * new tables, while the `down` changes include SQL statements for dropping the corresponding tables or
725
+ * recreating them if necessary.
726
+ *
727
+ * @return {Promise<SqlChanges>} A promise that resolves to an object containing `up` and `down` SQL
728
+ * change statements for migrating the database schema.
729
+ */
658
730
  async createAndDropTables() {
659
731
  let changes = {
660
732
  up: "\n\n-- Create tables\n",
@@ -681,6 +753,16 @@ ${foreignKeyOnQuery}`;
681
753
  }
682
754
  return changes;
683
755
  }
756
+ /**
757
+ * Generates an SQL string to create a table with the specified schema.
758
+ *
759
+ * @param tableName - The name of the table to be created.
760
+ * @param columns - An array of objects defining the columns of the table, including their names, types
761
+ * default values, and whether they are primary keys.
762
+ * @param foreignKeys - An optional array of objects defining the foreign key constraints for the table,
763
+ * including the source column, target table, target column, action on update, and action on delete.
764
+ * @return The complete SQL string to create the table with the specified structure and constraints.
765
+ */
684
766
  createTableSql(tableName, columns, foreignKeys) {
685
767
  const queryLines = [];
686
768
  for (const columnInfo of columns) {
@@ -693,9 +775,20 @@ ${foreignKeyOnQuery}`;
693
775
  }
694
776
  return this.dialect.createTable(tableName, queryLines);
695
777
  }
778
+ /**
779
+ * Checks if the specified table does not exist in the list of existing tables.
780
+ *
781
+ * @param tableName - The name of the table to check.
782
+ * @return Returns true if the table does not exist, otherwise false.
783
+ */
696
784
  tableDoesNotExists(tableName) {
697
785
  return this.existingTables.indexOf(tableName) == -1;
698
786
  }
787
+ /**
788
+ * Recreates database tables that have been flagged to be recreated in migration data.
789
+ * This method generated the necessary SQL code for backing up the current table data, dropping and recreating
790
+ * the tables, and reinserting the preserved data into the recreated tables.
791
+ */
699
792
  async recreateTables() {
700
793
  const changes = {
701
794
  up: "\n\n-- Recreate tables\n",
@@ -724,6 +817,9 @@ ${foreignKeyOnQuery}`;
724
817
  }
725
818
  return changes;
726
819
  }
820
+ /**
821
+ * Migrates foreign key constraints for all defined tables.
822
+ */
727
823
  async migrateForeignKeys() {
728
824
  if (!this.dialect.canAlterForeignKeys)
729
825
  return { up: "", down: "" };
@@ -847,12 +943,22 @@ ${foreignKeyOnQuery}`;
847
943
  }
848
944
  return changes;
849
945
  }
946
+ /**
947
+ * Retrieves the name of the primary key column from a list of column information.
948
+ *
949
+ * @param columnInfoList - An array of ColumnInfo objects containing details about each column.
950
+ * @return The name of the primary key column if found; otherwise, undefined.
951
+ */
850
952
  getPrimaryKeyColumn(columnInfoList) {
851
953
  for (const columnInfo of columnInfoList) {
852
954
  if (columnInfo.isPrimaryKey)
853
955
  return columnInfo.name;
854
956
  }
855
957
  }
958
+ /**
959
+ * Handles the renaming of columns in database tables by generating SQL changes for both applying and reverting the renames.
960
+ * Skips tables that are marked for recreation.
961
+ */
856
962
  renameColumns() {
857
963
  const changes = {
858
964
  up: "\n\n-- Rename columns\n",
@@ -914,7 +1020,8 @@ async function prepareMigration(db, instructions, overwriteExisting) {
914
1020
  async function runPreparedMigrations(db, instructions) {
915
1021
  Logger.setMode(instructions.loggerMode);
916
1022
  const migrationHistoryManager = new MigrationHistoryManager(instructions.configPath);
917
- const fromVersion = migrationHistoryManager.getLastHistoryVersion();
1023
+ const dialect = getDialect(instructions);
1024
+ const fromVersion = await dialect.getVersion(db);
918
1025
  const toVersion = instructions.version;
919
1026
  if (fromVersion == toVersion)
920
1027
  return;
@@ -925,17 +1032,25 @@ async function runPreparedMigrations(db, instructions) {
925
1032
  await db.runMultipleWriteStatements(upChanges);
926
1033
  instructions.version = i;
927
1034
  }
928
- migrationHistoryManager.setLastHistoryVersion(toVersion);
1035
+ await dialect.setVersion(db, toVersion);
929
1036
  }
930
1037
  async function prepareAndRunMigration(db, instructions, overwriteExisting) {
931
1038
  Logger.setMode(instructions.loggerMode);
932
1039
  await prepareMigration(db, instructions, overwriteExisting);
933
1040
  await runPreparedMigrations(db, instructions);
934
1041
  }
1042
+ async function runMigrationWithoutHistory(db, instructions) {
1043
+ Logger.setMode(instructions.loggerMode);
1044
+ const mm = new MigrationManager(db, instructions);
1045
+ const changes = await mm.getMigrateSql();
1046
+ if (changes)
1047
+ await db.runMultipleWriteStatements(changes.up);
1048
+ }
935
1049
  async function rollback(db, instructions, toVersion) {
936
1050
  Logger.setMode(instructions.loggerMode);
937
1051
  const migrationHistoryManager = new MigrationHistoryManager(instructions.configPath);
938
- const fromVersion = migrationHistoryManager.getLastHistoryVersion();
1052
+ const dialect = getDialect(instructions);
1053
+ const fromVersion = await dialect.getVersion(db);
939
1054
  Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
940
1055
  for (let i = fromVersion - 1; i >= toVersion; --i) {
941
1056
  const upChanges = migrationHistoryManager.getDownMigration(i);
@@ -943,7 +1058,7 @@ async function rollback(db, instructions, toVersion) {
943
1058
  await db.runMultipleWriteStatements(upChanges);
944
1059
  instructions.version = i;
945
1060
  }
946
- migrationHistoryManager.setLastHistoryVersion(toVersion);
1061
+ await dialect.setVersion(db, toVersion);
947
1062
  }
948
1063
  // Annotate the CommonJS export names for ESM import in node:
949
1064
  0 && (module.exports = {
@@ -952,5 +1067,6 @@ async function rollback(db, instructions, toVersion) {
952
1067
  prepareAndRunMigration,
953
1068
  prepareMigration,
954
1069
  rollback,
1070
+ runMigrationWithoutHistory,
955
1071
  runPreparedMigrations
956
1072
  });
package/dist/index.mjs CHANGED
@@ -83,7 +83,7 @@ var TableStructureGenerator = class {
83
83
  columnData.defaultValue = value === null ? this.dialect.typeNull : `"${this.dialect.formatValueToSql(value)}"`;
84
84
  break;
85
85
  case "number":
86
- columnData.type = this.dialect.typeNumber;
86
+ columnData.type = this.dialect.typeInt;
87
87
  columnData.defaultValue = value === null ? this.dialect.typeNull : this.dialect.formatValueToSql(value);
88
88
  break;
89
89
  case "boolean":
@@ -222,18 +222,15 @@ var MigrationHistoryManager = class {
222
222
  mkdirSync(this.configPath, { recursive: true });
223
223
  mkdirSync(this.migrationsPath, { recursive: true });
224
224
  }
225
- getLastHistoryVersion() {
226
- if (this.lastVersion)
227
- return this.lastVersion;
228
- const path = `${this.configPath}/last_version.txt`;
229
- this.lastVersion = existsSync(path) ? parseInt(readFileSync(`${this.configPath}/last_version.txt`, { encoding: "utf-8" })) ?? 0 : 0;
230
- return this.lastVersion;
231
- }
232
- setLastHistoryVersion(version) {
233
- writeFileSync(`${this.configPath}/last_version.txt`, version.toString(), { encoding: "utf-8" });
234
- }
235
- createMigrationHistory(toVersion, changes, overwriteExisting) {
236
- const fromVersion = this.getLastHistoryVersion();
225
+ /**
226
+ * Creates migration history files for upgrading and downgrading database schemas.
227
+ *
228
+ * @param fromVersion - The current version number of the database.
229
+ * @param toVersion - The target version number for the migration.
230
+ * @param changes - An object containing the SQL changes for upgrading (`changes.up`) and downgrading (`changes.down`).
231
+ * @param overwriteExisting - A flag that determines if existing migration files should be overwritten. Defaults to `false`.
232
+ */
233
+ createMigrationHistory(fromVersion, toVersion, changes, overwriteExisting) {
237
234
  const upPath = `${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`;
238
235
  const downPath = `${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${fromVersion}.sql`;
239
236
  if (existsSync(upPath) && !overwriteExisting)
@@ -248,22 +245,36 @@ ${changes.up}`, { encoding: "utf-8" });
248
245
  -- To: Version ${fromVersion}
249
246
  ${changes.down}`, { encoding: "utf-8" });
250
247
  }
248
+ /**
249
+ * Retrieves the SQL migration script for upgrading to the specified version.
250
+ * If no version is provided, it retrieves the migration script for the latest version in the history.
251
+ *
252
+ * @param toVersion - The version of the migration script to retrieve. If not specified, the latest version is used.
253
+ * @return The content of the SQL migration script as a string.
254
+ */
251
255
  getUpMigration(toVersion) {
252
- return readFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion ?? this.getLastHistoryVersion()}.sql`, { encoding: "utf-8" });
256
+ return readFileSync(`${this.migrationsPath}/${FILENAME_UP_PREFIX}${toVersion}.sql`, { encoding: "utf-8" });
253
257
  }
258
+ /**
259
+ * Retrieves the SQL migration script for downgrading to the specified version.
260
+ *
261
+ * @param version - The version number of the migration to retrieve.
262
+ * @return The SQL script for the down migration.
263
+ */
254
264
  getDownMigration(version) {
255
265
  return readFileSync(`${this.migrationsPath}/${FILENAME_DOWN_PREFIX}${version}.sql`, { encoding: "utf-8" });
256
266
  }
257
267
  };
258
268
 
259
269
  // src/lib/dialects/DefaultSql.ts
270
+ var MIGRATION_DATA_TABLE_NAME = "__sqlmorpheus_migrations";
260
271
  var DefaultSql = class {
261
272
  canAlterForeignKeys = false;
262
273
  canAlterPrimaryKey = false;
263
274
  canInspectForeignKeys = false;
264
275
  canInspectPrimaryKey = false;
265
276
  typeString = "TEXT";
266
- typeNumber = "INTEGER";
277
+ typeInt = "INTEGER";
267
278
  typeBoolean = "BOOLEAN";
268
279
  typeNull = "NULL";
269
280
  formatValueToSql(value) {
@@ -306,8 +317,8 @@ var DefaultSql = class {
306
317
  dropTable(tableName) {
307
318
  return `DROP TABLE IF EXISTS ${tableName};`;
308
319
  }
309
- columnDefinition(tableName, type, defaultValue, isPrimaryKey) {
310
- const query = `${tableName} ${type} DEFAULT ${defaultValue}`;
320
+ columnDefinition(columnName, type, defaultValue, isPrimaryKey) {
321
+ const query = `${columnName} ${type} DEFAULT ${defaultValue}`;
311
322
  return isPrimaryKey ? `${query} PRIMARY KEY` : query;
312
323
  }
313
324
  createColumn(columnTable, entry) {
@@ -334,6 +345,30 @@ var DefaultSql = class {
334
345
  insertValues(keys, valueString) {
335
346
  return `(${keys}) ${valueString ?? `VALUES (${keys.map(() => "?").join(",")})`}`;
336
347
  }
348
+ migrationTableQuery() {
349
+ return this.createTable(MIGRATION_DATA_TABLE_NAME, [
350
+ this.columnDefinition("version", this.typeInt, "0", false)
351
+ ]);
352
+ }
353
+ async createMigrationTableIfNeeded(db) {
354
+ await db.runMultipleWriteStatements(this.migrationTableQuery());
355
+ }
356
+ //TODO: untested
357
+ async getVersion(db) {
358
+ await this.createMigrationTableIfNeeded(db);
359
+ const query = this.select(MIGRATION_DATA_TABLE_NAME, ["version"]);
360
+ const data = await db.runGetStatement(query);
361
+ return data.length ? data[0].version : 0;
362
+ }
363
+ //TODO: untested
364
+ async setVersion(db, newVersion) {
365
+ const query = `${this.migrationTableQuery()};
366
+ INSERT INTO ${MIGRATION_DATA_TABLE_NAME} (version) VALUES ${newVersion} WHERE NOT EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME})
367
+ UNION ALL
368
+ UPDATE ${MIGRATION_DATA_TABLE_NAME} SET version = ${newVersion} WHERE EXISTS (SELECT 1 FROM ${MIGRATION_DATA_TABLE_NAME});
369
+ `;
370
+ await db.runMultipleWriteStatements(query);
371
+ }
337
372
  };
338
373
 
339
374
  // src/lib/dialects/SqliteDialect.ts
@@ -381,6 +416,13 @@ var SqliteDialect = class extends DefaultSql {
381
416
  return value.toString();
382
417
  }
383
418
  }
419
+ async getVersion(db) {
420
+ const output = await db.runGetStatement(`PRAGMA user_version;`);
421
+ return output[0].user_version;
422
+ }
423
+ async setVersion(db, newVersion) {
424
+ await db.runMultipleWriteStatements(`PRAGMA user_version = ${newVersion};`);
425
+ }
384
426
  };
385
427
 
386
428
  // src/lib/dialects/PostgresDialect.ts
@@ -519,6 +561,22 @@ var MySqlDialect = class extends DefaultSql {
519
561
  }
520
562
  };
521
563
 
564
+ // src/lib/getDialect.ts
565
+ function getDialect(dbInstructions) {
566
+ switch (dbInstructions.dialect) {
567
+ case "Sqlite":
568
+ return new SqliteDialect();
569
+ case "Postgres":
570
+ return new PostgresDialect();
571
+ case "MsSql":
572
+ return new MsSqlDialect();
573
+ case "MySql":
574
+ return new MySqlDialect();
575
+ default:
576
+ throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
577
+ }
578
+ }
579
+
522
580
  // src/lib/MigrationManager.ts
523
581
  var MigrationManager = class {
524
582
  migrations = new Migrations();
@@ -529,31 +587,27 @@ var MigrationManager = class {
529
587
  migrationHistoryManager;
530
588
  dialect;
531
589
  constructor(db, dbInstructions) {
532
- switch (dbInstructions.dialect) {
533
- case "Sqlite":
534
- this.dialect = new SqliteDialect();
535
- break;
536
- case "Postgres":
537
- this.dialect = new PostgresDialect();
538
- break;
539
- case "MsSql":
540
- this.dialect = new MsSqlDialect();
541
- break;
542
- case "MySql":
543
- this.dialect = new MySqlDialect();
544
- break;
545
- default:
546
- throw new Error(`Unknown dialect ${dbInstructions.dialect}`);
547
- }
590
+ this.dialect = getDialect(dbInstructions);
548
591
  this.db = db;
549
592
  this.dbInstructions = dbInstructions;
550
593
  this.tableStructureGenerator = new TableStructureGenerator(dbInstructions, this.dialect);
551
594
  this.migrationHistoryManager = new MigrationHistoryManager(this.dbInstructions.configPath);
552
595
  }
596
+ /**
597
+ * Generates the SQL changes required to migrate a database from its current version
598
+ * to the target version defined in the database instructions. Handles the creation
599
+ * and deletion of tables, migration of foreign keys and columns, renaming of columns,
600
+ * and recreating tables, along with executing any pre- and post-migration custom logic.
601
+ *
602
+ * @return A promise that resolves to an object containing the SQL
603
+ * changes for the migration (`up` and `down` changes) or null if no migration is required.
604
+ * Throws an error in scenarios such as attempting to migrate to a lower version
605
+ * or an invalid starting version.
606
+ */
553
607
  async getMigrateSql() {
554
608
  if (this.dbInstructions.version <= 0)
555
609
  throw new Error("Cannot migrate to version 0 or lower");
556
- const fromVersion = this.migrationHistoryManager.getLastHistoryVersion();
610
+ const fromVersion = await this.dialect.getVersion(this.db);
557
611
  if (fromVersion == 0) {
558
612
  Logger.log(`Creating initial migration to version ${this.dbInstructions.version}`);
559
613
  return this.createAndDropTables();
@@ -618,12 +672,29 @@ ${foreignKeyOnQuery}`;
618
672
  ${foreignKeyOnQuery}`;
619
673
  return changes;
620
674
  }
675
+ /**
676
+ * Prepares the migration by generating the SQL changes and creating a migration history entry.
677
+ * If no changes are needed, the method exits early.
678
+ *
679
+ * @param overwriteExisting - Optional flag indicating if overwriting existing changes is allowed.
680
+ * @return - A promise that resolves once the migration preparation is complete.
681
+ */
621
682
  async prepareMigration(overwriteExisting) {
622
683
  const changes = await this.getMigrateSql();
623
684
  if (!changes)
624
685
  return;
625
- this.migrationHistoryManager.createMigrationHistory(this.dbInstructions.version, changes, overwriteExisting);
686
+ const fromVersion = await this.dialect.getVersion(this.db);
687
+ this.migrationHistoryManager.createMigrationHistory(fromVersion, this.dbInstructions.version, changes, overwriteExisting);
626
688
  }
689
+ /**
690
+ * Generates SQL change statements for creating and dropping tables based on the current table structure
691
+ * and existing table configurations in the database. The `up` changes include SQL statements for creating
692
+ * new tables, while the `down` changes include SQL statements for dropping the corresponding tables or
693
+ * recreating them if necessary.
694
+ *
695
+ * @return {Promise<SqlChanges>} A promise that resolves to an object containing `up` and `down` SQL
696
+ * change statements for migrating the database schema.
697
+ */
627
698
  async createAndDropTables() {
628
699
  let changes = {
629
700
  up: "\n\n-- Create tables\n",
@@ -650,6 +721,16 @@ ${foreignKeyOnQuery}`;
650
721
  }
651
722
  return changes;
652
723
  }
724
+ /**
725
+ * Generates an SQL string to create a table with the specified schema.
726
+ *
727
+ * @param tableName - The name of the table to be created.
728
+ * @param columns - An array of objects defining the columns of the table, including their names, types
729
+ * default values, and whether they are primary keys.
730
+ * @param foreignKeys - An optional array of objects defining the foreign key constraints for the table,
731
+ * including the source column, target table, target column, action on update, and action on delete.
732
+ * @return The complete SQL string to create the table with the specified structure and constraints.
733
+ */
653
734
  createTableSql(tableName, columns, foreignKeys) {
654
735
  const queryLines = [];
655
736
  for (const columnInfo of columns) {
@@ -662,9 +743,20 @@ ${foreignKeyOnQuery}`;
662
743
  }
663
744
  return this.dialect.createTable(tableName, queryLines);
664
745
  }
746
+ /**
747
+ * Checks if the specified table does not exist in the list of existing tables.
748
+ *
749
+ * @param tableName - The name of the table to check.
750
+ * @return Returns true if the table does not exist, otherwise false.
751
+ */
665
752
  tableDoesNotExists(tableName) {
666
753
  return this.existingTables.indexOf(tableName) == -1;
667
754
  }
755
+ /**
756
+ * Recreates database tables that have been flagged to be recreated in migration data.
757
+ * This method generated the necessary SQL code for backing up the current table data, dropping and recreating
758
+ * the tables, and reinserting the preserved data into the recreated tables.
759
+ */
668
760
  async recreateTables() {
669
761
  const changes = {
670
762
  up: "\n\n-- Recreate tables\n",
@@ -693,6 +785,9 @@ ${foreignKeyOnQuery}`;
693
785
  }
694
786
  return changes;
695
787
  }
788
+ /**
789
+ * Migrates foreign key constraints for all defined tables.
790
+ */
696
791
  async migrateForeignKeys() {
697
792
  if (!this.dialect.canAlterForeignKeys)
698
793
  return { up: "", down: "" };
@@ -816,12 +911,22 @@ ${foreignKeyOnQuery}`;
816
911
  }
817
912
  return changes;
818
913
  }
914
+ /**
915
+ * Retrieves the name of the primary key column from a list of column information.
916
+ *
917
+ * @param columnInfoList - An array of ColumnInfo objects containing details about each column.
918
+ * @return The name of the primary key column if found; otherwise, undefined.
919
+ */
819
920
  getPrimaryKeyColumn(columnInfoList) {
820
921
  for (const columnInfo of columnInfoList) {
821
922
  if (columnInfo.isPrimaryKey)
822
923
  return columnInfo.name;
823
924
  }
824
925
  }
926
+ /**
927
+ * Handles the renaming of columns in database tables by generating SQL changes for both applying and reverting the renames.
928
+ * Skips tables that are marked for recreation.
929
+ */
825
930
  renameColumns() {
826
931
  const changes = {
827
932
  up: "\n\n-- Rename columns\n",
@@ -883,7 +988,8 @@ async function prepareMigration(db, instructions, overwriteExisting) {
883
988
  async function runPreparedMigrations(db, instructions) {
884
989
  Logger.setMode(instructions.loggerMode);
885
990
  const migrationHistoryManager = new MigrationHistoryManager(instructions.configPath);
886
- const fromVersion = migrationHistoryManager.getLastHistoryVersion();
991
+ const dialect = getDialect(instructions);
992
+ const fromVersion = await dialect.getVersion(db);
887
993
  const toVersion = instructions.version;
888
994
  if (fromVersion == toVersion)
889
995
  return;
@@ -894,17 +1000,25 @@ async function runPreparedMigrations(db, instructions) {
894
1000
  await db.runMultipleWriteStatements(upChanges);
895
1001
  instructions.version = i;
896
1002
  }
897
- migrationHistoryManager.setLastHistoryVersion(toVersion);
1003
+ await dialect.setVersion(db, toVersion);
898
1004
  }
899
1005
  async function prepareAndRunMigration(db, instructions, overwriteExisting) {
900
1006
  Logger.setMode(instructions.loggerMode);
901
1007
  await prepareMigration(db, instructions, overwriteExisting);
902
1008
  await runPreparedMigrations(db, instructions);
903
1009
  }
1010
+ async function runMigrationWithoutHistory(db, instructions) {
1011
+ Logger.setMode(instructions.loggerMode);
1012
+ const mm = new MigrationManager(db, instructions);
1013
+ const changes = await mm.getMigrateSql();
1014
+ if (changes)
1015
+ await db.runMultipleWriteStatements(changes.up);
1016
+ }
904
1017
  async function rollback(db, instructions, toVersion) {
905
1018
  Logger.setMode(instructions.loggerMode);
906
1019
  const migrationHistoryManager = new MigrationHistoryManager(instructions.configPath);
907
- const fromVersion = migrationHistoryManager.getLastHistoryVersion();
1020
+ const dialect = getDialect(instructions);
1021
+ const fromVersion = await dialect.getVersion(db);
908
1022
  Logger.log(`Rolling back from ${fromVersion} to ${toVersion}`);
909
1023
  for (let i = fromVersion - 1; i >= toVersion; --i) {
910
1024
  const upChanges = migrationHistoryManager.getDownMigration(i);
@@ -912,7 +1026,7 @@ async function rollback(db, instructions, toVersion) {
912
1026
  await db.runMultipleWriteStatements(upChanges);
913
1027
  instructions.version = i;
914
1028
  }
915
- migrationHistoryManager.setLastHistoryVersion(toVersion);
1029
+ await dialect.setVersion(db, toVersion);
916
1030
  }
917
1031
  export {
918
1032
  DbTable,
@@ -920,5 +1034,6 @@ export {
920
1034
  prepareAndRunMigration,
921
1035
  prepareMigration,
922
1036
  rollback,
1037
+ runMigrationWithoutHistory,
923
1038
  runPreparedMigrations
924
1039
  };
package/package.json CHANGED
@@ -1,39 +1,39 @@
1
- {
2
- "name": "sqlmorpheus",
3
- "version": "0.1.4",
4
- "author": "JodliDev",
5
- "keywords": [],
6
- "license": "MIT",
7
- "description": "",
8
- "repository": {
9
- "type": "git",
10
- "url": "git://github.com/JodliDev/SqlMorpheus.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/JodliDev/SqlMorpheus/issues"
14
- },
15
- "homepage": "https://github.com/JodliDev/SqlMorpheus#readme",
16
- "main": "dist/index.js",
17
- "types": "dist/index.d.ts",
18
- "files": [
19
- "/dist"
20
- ],
21
- "scripts": {
22
- "build": "tsup",
23
- "dev": "tsup --watch",
24
- "test": "vitest run",
25
- "test:watch": "vitest",
26
- "publish": "npm publish"
27
- },
28
- "devDependencies": {
29
- "@types/better-sqlite3": "^7.6.13",
30
- "@types/node": "^24.0.13",
31
- "better-sqlite3": "^12.2.0",
32
- "tsup": "^8.5.0",
33
- "typescript": "^5.8.3",
34
- "vitest": "^3.2.4"
35
- },
36
- "dependencies": {
37
- "polyfill-symbol-metadata": "^0.0.2"
38
- }
39
- }
1
+ {
2
+ "name": "sqlmorpheus",
3
+ "version": "0.1.6",
4
+ "author": "JodliDev",
5
+ "keywords": [],
6
+ "license": "MIT",
7
+ "description": "",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git://github.com/JodliDev/SqlMorpheus.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/JodliDev/SqlMorpheus/issues"
14
+ },
15
+ "homepage": "https://github.com/JodliDev/SqlMorpheus#readme",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "files": [
19
+ "/dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "run_publish": "npm publish"
27
+ },
28
+ "devDependencies": {
29
+ "@types/better-sqlite3": "^7.6.13",
30
+ "@types/node": "^24.0.13",
31
+ "better-sqlite3": "^12.2.0",
32
+ "tsup": "^8.5.0",
33
+ "typescript": "^5.8.3",
34
+ "vitest": "^3.2.4"
35
+ },
36
+ "dependencies": {
37
+ "polyfill-symbol-metadata": "^0.0.2"
38
+ }
39
+ }