@ghom/orm 2.0.0 → 2.1.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/biome.json CHANGED
@@ -24,6 +24,9 @@
24
24
  "enabled": true,
25
25
  "rules": {
26
26
  "recommended": true,
27
+ "complexity": {
28
+ "noBannedTypes": "off"
29
+ },
27
30
  "suspicious": {
28
31
  "noExplicitAny": "off",
29
32
  "noThenProperty": "off",
@@ -32,6 +35,10 @@
32
35
  "style": {
33
36
  "noNonNullAssertion": "off",
34
37
  "useNodejsImportProtocol": "off"
38
+ },
39
+ "correctness": {
40
+ "noUnusedVariables": "off",
41
+ "noUnusedImports": "off"
35
42
  }
36
43
  }
37
44
  },
@@ -0,0 +1,258 @@
1
+ import type { Knex } from "knex";
2
+ import { type ColumnDef, type InferColumns, type InferColumnType } from "./column.js";
3
+ /**
4
+ * Represents a typed migration that transforms the table schema.
5
+ * Carries type information about what columns are removed and added.
6
+ *
7
+ * @template From - The type of columns being removed/modified
8
+ * @template To - The type of columns being added/modified
9
+ */
10
+ export interface TypedMigration<From = {}, To = {}> {
11
+ /** @internal Type marker for columns being removed */
12
+ readonly _from: From;
13
+ /** @internal Type marker for columns being added */
14
+ readonly _to: To;
15
+ /**
16
+ * Apply the migration to the table builder.
17
+ */
18
+ apply: (builder: Knex.AlterTableBuilder) => void;
19
+ }
20
+ /**
21
+ * Represents a sequence of typed migrations.
22
+ * Used internally by migrate.sequence() to preserve individual migration types.
23
+ *
24
+ * @template Migrations - Tuple of migrations in the sequence
25
+ */
26
+ export interface TypedMigrationSequence<Migrations extends TypedMigration<any, any>[]> {
27
+ /** @internal The individual migrations in the sequence */
28
+ readonly __migrations__: Migrations;
29
+ /** @internal Type marker for columns being removed (computed from sequence) */
30
+ readonly _from: SequenceFromType<Migrations>;
31
+ /** @internal Type marker for columns being added (computed from sequence) */
32
+ readonly _to: SequenceToType<Migrations>;
33
+ /**
34
+ * Apply all migrations in sequence to the table builder.
35
+ */
36
+ apply: (builder: Knex.AlterTableBuilder) => void;
37
+ }
38
+ /**
39
+ * Unwrap a migration array to get the individual migration types.
40
+ * If M is an array, extracts the element type; otherwise returns M as-is.
41
+ */
42
+ type UnwrapMigrationArray<M> = M extends readonly (infer U)[] ? U : M;
43
+ /**
44
+ * Extract "From" keys from a single TypedMigration or TypedMigrationSequence.
45
+ * For sequences, excludes intermediate columns (those that are also in _to).
46
+ */
47
+ type ExtractFromKeysSingle<M> = M extends {
48
+ __migrations__: infer Migrations;
49
+ } ? Migrations extends TypedMigration<any, any>[] ? Exclude<keyof UnionToIntersection<Migrations[number]["_from"]>, keyof UnionToIntersection<Migrations[number]["_to"]>> : never : M extends TypedMigration<infer From, any> ? keyof From : never;
50
+ /**
51
+ * Extract all "From" keys from a union of TypedMigration.
52
+ * These are the columns that will be removed/renamed.
53
+ * Handles both single migrations and arrays of migrations.
54
+ * Uses distributive conditional types to handle unions correctly.
55
+ */
56
+ type ExtractFromKeys<M> = M extends any ? UnwrapMigrationArray<M> extends infer U ? U extends any ? ExtractFromKeysSingle<U> : never : never : never;
57
+ /**
58
+ * Extract "To" type from a single TypedMigration or TypedMigrationSequence.
59
+ * For sequences, excludes intermediate columns (those that are also in _from).
60
+ */
61
+ type ExtractToTypesSingle<M> = M extends {
62
+ __migrations__: infer Migrations;
63
+ } ? Migrations extends TypedMigration<any, any>[] ? Omit<UnionToIntersection<Migrations[number]["_to"]>, keyof UnionToIntersection<Migrations[number]["_from"]>> : never : M extends TypedMigration<any, infer To> ? To : never;
64
+ /**
65
+ * Extract all "To" types from a union of TypedMigration and intersect them.
66
+ * These are the columns that will be added.
67
+ * Handles both single migrations and arrays of migrations.
68
+ * Uses distributive conditional types to handle unions correctly.
69
+ */
70
+ type ExtractToTypes<M> = M extends any ? UnwrapMigrationArray<M> extends infer U ? U extends any ? ExtractToTypesSingle<U> : never : never : never;
71
+ /**
72
+ * Convert a union to an intersection.
73
+ * Used to combine all "To" types from migrations.
74
+ */
75
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
76
+ /**
77
+ * Force TypeScript to evaluate a type (expands type aliases).
78
+ */
79
+ type Simplify<T> = {
80
+ [K in keyof T]: T[K];
81
+ } & {};
82
+ /**
83
+ * Combine all "from" types from a tuple of migrations.
84
+ */
85
+ type CombineFromTypes<T extends TypedMigration<any, any>[]> = UnionToIntersection<T[number]["_from"]>;
86
+ /**
87
+ * Combine all "to" types from a tuple of migrations.
88
+ */
89
+ type CombineToTypes<T extends TypedMigration<any, any>[]> = UnionToIntersection<T[number]["_to"]>;
90
+ /**
91
+ * Compute the final "from" type for a sequence of migrations.
92
+ * Excludes columns that are also added (intermediate renames).
93
+ */
94
+ type SequenceFromType<T extends TypedMigration<any, any>[]> = Simplify<Omit<CombineFromTypes<T>, keyof CombineToTypes<T>>>;
95
+ /**
96
+ * Compute the final "to" type for a sequence of migrations.
97
+ * Excludes columns that are also removed (intermediate renames).
98
+ */
99
+ type SequenceToType<T extends TypedMigration<any, any>[]> = Simplify<Omit<CombineToTypes<T>, keyof CombineFromTypes<T>>>;
100
+ /**
101
+ * Apply all migrations to compute the final type.
102
+ * 1. Remove all columns specified in migration "From" types
103
+ * 2. Add all columns specified in migration "To" types
104
+ * Handles both single migrations and arrays of migrations.
105
+ */
106
+ export type ApplyMigrations<Base, Migrations extends Record<string, any>> = Migrations[keyof Migrations] extends infer M ? Omit<Base, ExtractFromKeys<M>> & UnionToIntersection<ExtractToTypes<M>> : Base;
107
+ /**
108
+ * Compute the final table type from base columns and migrations.
109
+ * Supports both single migrations and arrays of migrations.
110
+ */
111
+ export type FinalTableType<Columns extends Record<string, ColumnDef<any, any>>, Migrations extends Record<string, any> = {}> = ApplyMigrations<InferColumns<Columns>, Migrations>;
112
+ /**
113
+ * Migration helpers for creating typed migrations.
114
+ * Each helper returns a TypedMigration with appropriate type transformations.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * import { migrate, col } from "@ghom/orm"
119
+ *
120
+ * const userTable = new Table({
121
+ * name: "user",
122
+ * columns: (col) => ({
123
+ * id: col.increments(),
124
+ * name: col.string(),
125
+ * }),
126
+ * migrations: {
127
+ * "001_rename_name": migrate.renameColumn("name", "username"),
128
+ * "002_add_email": migrate.addColumn("email", col.string()),
129
+ * },
130
+ * })
131
+ * ```
132
+ */
133
+ export declare const migrate: {
134
+ /**
135
+ * Add a new column to the table.
136
+ *
137
+ * @param name - The column name
138
+ * @param column - The column definition
139
+ * @returns A typed migration that adds the column
140
+ *
141
+ * @example
142
+ * migrate.addColumn("email", col.string())
143
+ * // Adds: { email: string }
144
+ */
145
+ addColumn<K extends string, C extends ColumnDef<any, any>>(name: K, column: C): TypedMigration<{}, { [P in K]: InferColumnType<C>; }>;
146
+ /**
147
+ * Drop a column from the table.
148
+ *
149
+ * @param name - The column name to drop
150
+ * @returns A typed migration that removes the column
151
+ *
152
+ * @example
153
+ * migrate.dropColumn("oldField")
154
+ * // Removes: { oldField: any }
155
+ */
156
+ dropColumn<K extends string>(name: K): TypedMigration<{ [P in K]: unknown; }, {}>;
157
+ /**
158
+ * Rename a column.
159
+ *
160
+ * @param oldName - The current column name
161
+ * @param newName - The new column name
162
+ * @returns A typed migration that renames the column
163
+ *
164
+ * @example
165
+ * migrate.renameColumn("name", "username")
166
+ * // Removes: { name: any }, Adds: { username: any }
167
+ */
168
+ renameColumn<Old extends string, New extends string>(oldName: Old, newName: New): TypedMigration<{ [P in Old]: unknown; }, { [P in New]: unknown; }>;
169
+ /**
170
+ * Alter a column's type or constraints.
171
+ *
172
+ * @param name - The column name
173
+ * @param column - The new column definition
174
+ * @returns A typed migration that alters the column
175
+ *
176
+ * @example
177
+ * migrate.alterColumn("age", col.integer().nullable())
178
+ * // Changes type: { age: number | null }
179
+ */
180
+ alterColumn<K extends string, C extends ColumnDef<any, any>>(name: K, column: C): TypedMigration<{ [P in K]: unknown; }, { [P in K]: InferColumnType<C>; }>;
181
+ /**
182
+ * Add an index on one or more columns.
183
+ *
184
+ * @param columns - Array of column names to index
185
+ * @param name - Optional index name
186
+ * @returns A typed migration (no type change)
187
+ *
188
+ * @example
189
+ * migrate.addIndex(["email"], "idx_email")
190
+ */
191
+ addIndex(columns: string[], name?: string): TypedMigration<{}, {}>;
192
+ /**
193
+ * Drop an index by name.
194
+ *
195
+ * @param name - The index name to drop
196
+ * @returns A typed migration (no type change)
197
+ *
198
+ * @example
199
+ * migrate.dropIndex("idx_email")
200
+ */
201
+ dropIndex(name: string): TypedMigration<{}, {}>;
202
+ /**
203
+ * Add a unique constraint on one or more columns.
204
+ *
205
+ * @param columns - Array of column names
206
+ * @param name - Optional constraint name
207
+ * @returns A typed migration (no type change)
208
+ *
209
+ * @example
210
+ * migrate.addUnique(["email"], "uniq_email")
211
+ */
212
+ addUnique(columns: string[], name?: string): TypedMigration<{}, {}>;
213
+ /**
214
+ * Drop a unique constraint by name.
215
+ *
216
+ * @param name - The constraint name to drop
217
+ * @returns A typed migration (no type change)
218
+ *
219
+ * @example
220
+ * migrate.dropUnique("uniq_email")
221
+ */
222
+ dropUnique(name: string): TypedMigration<{}, {}>;
223
+ /**
224
+ * Custom migration with a raw callback.
225
+ * Use this when the built-in helpers don't cover your use case.
226
+ *
227
+ * @param fn - The migration callback
228
+ * @returns A typed migration (no type change by default)
229
+ *
230
+ * @example
231
+ * migrate.raw((builder) => {
232
+ * builder.dropColumn("temp")
233
+ * builder.string("new_col")
234
+ * })
235
+ */
236
+ raw<From = {}, To = {}>(fn: (builder: Knex.AlterTableBuilder) => void): TypedMigration<From, To>;
237
+ /**
238
+ * Combine multiple migrations into a single migration.
239
+ * All migrations are applied sequentially within the same alter table call.
240
+ * Type information from all migrations is preserved and combined.
241
+ *
242
+ * Intermediate columns (added then removed in the sequence) are excluded from the final type.
243
+ * For example: renameColumn("a", "b") + renameColumn("b", "c") results in only "c" being added.
244
+ *
245
+ * @param migrations - The migrations to combine
246
+ * @returns A typed migration sequence combining all type transformations
247
+ *
248
+ * @example
249
+ * migrate.sequence(
250
+ * migrate.addColumn("phone", col.string()),
251
+ * migrate.addColumn("address", col.string().nullable()),
252
+ * migrate.addIndex(["phone"]),
253
+ * )
254
+ * // Combines: removes nothing, adds { phone: string; address: string | null }
255
+ */
256
+ sequence<T extends TypedMigration<any, any>[]>(...migrations: T): TypedMigrationSequence<T>;
257
+ };
258
+ export {};
@@ -0,0 +1,229 @@
1
+ import { buildColumnsSchema, } from "./column.js";
2
+ /**
3
+ * Migration helpers for creating typed migrations.
4
+ * Each helper returns a TypedMigration with appropriate type transformations.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { migrate, col } from "@ghom/orm"
9
+ *
10
+ * const userTable = new Table({
11
+ * name: "user",
12
+ * columns: (col) => ({
13
+ * id: col.increments(),
14
+ * name: col.string(),
15
+ * }),
16
+ * migrations: {
17
+ * "001_rename_name": migrate.renameColumn("name", "username"),
18
+ * "002_add_email": migrate.addColumn("email", col.string()),
19
+ * },
20
+ * })
21
+ * ```
22
+ */
23
+ export const migrate = {
24
+ /**
25
+ * Add a new column to the table.
26
+ *
27
+ * @param name - The column name
28
+ * @param column - The column definition
29
+ * @returns A typed migration that adds the column
30
+ *
31
+ * @example
32
+ * migrate.addColumn("email", col.string())
33
+ * // Adds: { email: string }
34
+ */
35
+ addColumn(name, column) {
36
+ return {
37
+ _from: {},
38
+ _to: {},
39
+ apply: (builder) => {
40
+ buildColumnsSchema(builder, { [name]: column });
41
+ },
42
+ };
43
+ },
44
+ /**
45
+ * Drop a column from the table.
46
+ *
47
+ * @param name - The column name to drop
48
+ * @returns A typed migration that removes the column
49
+ *
50
+ * @example
51
+ * migrate.dropColumn("oldField")
52
+ * // Removes: { oldField: any }
53
+ */
54
+ dropColumn(name) {
55
+ return {
56
+ _from: {},
57
+ _to: {},
58
+ apply: (builder) => {
59
+ builder.dropColumn(name);
60
+ },
61
+ };
62
+ },
63
+ /**
64
+ * Rename a column.
65
+ *
66
+ * @param oldName - The current column name
67
+ * @param newName - The new column name
68
+ * @returns A typed migration that renames the column
69
+ *
70
+ * @example
71
+ * migrate.renameColumn("name", "username")
72
+ * // Removes: { name: any }, Adds: { username: any }
73
+ */
74
+ renameColumn(oldName, newName) {
75
+ return {
76
+ _from: {},
77
+ _to: {},
78
+ apply: (builder) => {
79
+ builder.renameColumn(oldName, newName);
80
+ },
81
+ };
82
+ },
83
+ /**
84
+ * Alter a column's type or constraints.
85
+ *
86
+ * @param name - The column name
87
+ * @param column - The new column definition
88
+ * @returns A typed migration that alters the column
89
+ *
90
+ * @example
91
+ * migrate.alterColumn("age", col.integer().nullable())
92
+ * // Changes type: { age: number | null }
93
+ */
94
+ alterColumn(name, column) {
95
+ return {
96
+ _from: {},
97
+ _to: {},
98
+ apply: (builder) => {
99
+ builder.dropColumn(name);
100
+ buildColumnsSchema(builder, { [name]: column });
101
+ },
102
+ };
103
+ },
104
+ /**
105
+ * Add an index on one or more columns.
106
+ *
107
+ * @param columns - Array of column names to index
108
+ * @param name - Optional index name
109
+ * @returns A typed migration (no type change)
110
+ *
111
+ * @example
112
+ * migrate.addIndex(["email"], "idx_email")
113
+ */
114
+ addIndex(columns, name) {
115
+ return {
116
+ _from: {},
117
+ _to: {},
118
+ apply: (builder) => {
119
+ builder.index(columns, name);
120
+ },
121
+ };
122
+ },
123
+ /**
124
+ * Drop an index by name.
125
+ *
126
+ * @param name - The index name to drop
127
+ * @returns A typed migration (no type change)
128
+ *
129
+ * @example
130
+ * migrate.dropIndex("idx_email")
131
+ */
132
+ dropIndex(name) {
133
+ return {
134
+ _from: {},
135
+ _to: {},
136
+ apply: (builder) => {
137
+ builder.dropIndex([], name);
138
+ },
139
+ };
140
+ },
141
+ /**
142
+ * Add a unique constraint on one or more columns.
143
+ *
144
+ * @param columns - Array of column names
145
+ * @param name - Optional constraint name
146
+ * @returns A typed migration (no type change)
147
+ *
148
+ * @example
149
+ * migrate.addUnique(["email"], "uniq_email")
150
+ */
151
+ addUnique(columns, name) {
152
+ return {
153
+ _from: {},
154
+ _to: {},
155
+ apply: (builder) => {
156
+ builder.unique(columns, { indexName: name });
157
+ },
158
+ };
159
+ },
160
+ /**
161
+ * Drop a unique constraint by name.
162
+ *
163
+ * @param name - The constraint name to drop
164
+ * @returns A typed migration (no type change)
165
+ *
166
+ * @example
167
+ * migrate.dropUnique("uniq_email")
168
+ */
169
+ dropUnique(name) {
170
+ return {
171
+ _from: {},
172
+ _to: {},
173
+ apply: (builder) => {
174
+ builder.dropUnique([], name);
175
+ },
176
+ };
177
+ },
178
+ /**
179
+ * Custom migration with a raw callback.
180
+ * Use this when the built-in helpers don't cover your use case.
181
+ *
182
+ * @param fn - The migration callback
183
+ * @returns A typed migration (no type change by default)
184
+ *
185
+ * @example
186
+ * migrate.raw((builder) => {
187
+ * builder.dropColumn("temp")
188
+ * builder.string("new_col")
189
+ * })
190
+ */
191
+ raw(fn) {
192
+ return {
193
+ _from: {},
194
+ _to: {},
195
+ apply: fn,
196
+ };
197
+ },
198
+ /**
199
+ * Combine multiple migrations into a single migration.
200
+ * All migrations are applied sequentially within the same alter table call.
201
+ * Type information from all migrations is preserved and combined.
202
+ *
203
+ * Intermediate columns (added then removed in the sequence) are excluded from the final type.
204
+ * For example: renameColumn("a", "b") + renameColumn("b", "c") results in only "c" being added.
205
+ *
206
+ * @param migrations - The migrations to combine
207
+ * @returns A typed migration sequence combining all type transformations
208
+ *
209
+ * @example
210
+ * migrate.sequence(
211
+ * migrate.addColumn("phone", col.string()),
212
+ * migrate.addColumn("address", col.string().nullable()),
213
+ * migrate.addIndex(["phone"]),
214
+ * )
215
+ * // Combines: removes nothing, adds { phone: string; address: string | null }
216
+ */
217
+ sequence(...migrations) {
218
+ return {
219
+ __migrations__: migrations,
220
+ _from: {},
221
+ _to: {},
222
+ apply: (builder) => {
223
+ for (const migration of migrations) {
224
+ migration.apply(builder);
225
+ }
226
+ },
227
+ };
228
+ },
229
+ };
package/dist/app/orm.d.ts CHANGED
@@ -43,6 +43,21 @@ export interface ORMConfig {
43
43
  * Default is `Infinity`.
44
44
  */
45
45
  caching?: number;
46
+ /**
47
+ * Configuration for migration behavior.
48
+ */
49
+ migrations?: {
50
+ /**
51
+ * Force alphabetical sorting for string migration keys instead of insertion order.
52
+ *
53
+ * **NOT RECOMMENDED**: If your keys start with numbers (e.g., "001_init", "002_add_users"),
54
+ * they are automatically sorted by those numbers, not alphabetically.
55
+ * Prefer insertion order or purely numeric keys instead.
56
+ *
57
+ * @default false
58
+ */
59
+ alphabeticalOrder?: boolean;
60
+ };
46
61
  }
47
62
  /**
48
63
  * The main ORM class that manages database connections, tables, and caching.
@@ -85,7 +100,7 @@ export declare class ORM {
85
100
  * Returns true if the ORM has a database client connected.
86
101
  */
87
102
  get isConnected(): boolean;
88
- get cachedTables(): Table<any>[];
103
+ get cachedTables(): Table<any, {}>[];
89
104
  get cachedTableNames(): string[];
90
105
  hasCachedTable(name: string): boolean;
91
106
  hasTable(name: string): Promise<boolean>;
@@ -109,4 +124,9 @@ export declare class ORM {
109
124
  * @warning This will delete all the data in the tables.
110
125
  */
111
126
  restoreBackup(dirname?: string): Promise<void>;
127
+ /**
128
+ * Upgrade the migration table from integer version to string version.
129
+ * This is needed for projects that were using the old migration system.
130
+ */
131
+ private upgradeMigrationTableIfNeeded;
112
132
  }
package/dist/app/orm.js CHANGED
@@ -5,6 +5,16 @@ import { default as knex } from "knex";
5
5
  import { backupTable, disableForeignKeys, enableForeignKeys, restoreBackup } from "./backup.js";
6
6
  import { Table } from "./table.js";
7
7
  import { isCJS } from "./util.js";
8
+ /**
9
+ * Verify that the environment supports ES2015+ object key ordering.
10
+ * In ES2015+, integer keys are sorted numerically first, then string keys
11
+ * maintain their insertion order.
12
+ */
13
+ function checkES2015KeyOrder() {
14
+ const test = { "2": "a", "1": "b", c: "d" };
15
+ const keys = Object.keys(test);
16
+ return keys[0] === "1" && keys[1] === "2" && keys[2] === "c";
17
+ }
8
18
  /**
9
19
  * The main ORM class that manages database connections, tables, and caching.
10
20
  *
@@ -41,6 +51,9 @@ export class ORM {
41
51
  */
42
52
  constructor(config) {
43
53
  this.config = config;
54
+ if (!checkES2015KeyOrder()) {
55
+ throw new Error("@ghom/orm requires ES2015+ environment for guaranteed object key ordering");
56
+ }
44
57
  if (config === false)
45
58
  return;
46
59
  this._client = knex(config.database ?? {
@@ -105,9 +118,11 @@ export class ORM {
105
118
  priority: Infinity,
106
119
  columns: (col) => ({
107
120
  table: col.string().unique(),
108
- version: col.integer(),
121
+ version: col.string(),
109
122
  }),
110
123
  }));
124
+ // Auto-migrate version column from integer to string for existing projects
125
+ await this.upgradeMigrationTableIfNeeded();
111
126
  const sortedTables = this.cachedTables.toSorted((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
112
127
  for (const table of sortedTables) {
113
128
  await table.make(this);
@@ -162,4 +177,39 @@ export class ORM {
162
177
  });
163
178
  console.log("Database restored from backup.");
164
179
  }
180
+ /**
181
+ * Upgrade the migration table from integer version to string version.
182
+ * This is needed for projects that were using the old migration system.
183
+ */
184
+ async upgradeMigrationTableIfNeeded() {
185
+ this.requireClient();
186
+ const hasMigrationTable = await this._client.schema.hasTable("migration");
187
+ if (!hasMigrationTable)
188
+ return;
189
+ const columnInfo = await this._client("migration").columnInfo("version");
190
+ const columnType = columnInfo?.type ?? "";
191
+ // Check if version column is integer type (varies by database)
192
+ const isIntegerType = columnType.includes("int") ||
193
+ columnType.includes("INT") ||
194
+ columnType === "integer" ||
195
+ columnType === "INTEGER";
196
+ if (!isIntegerType)
197
+ return;
198
+ // Migrate: convert integer versions to strings
199
+ // SQLite doesn't support column alterations well, so we use a temp column approach
200
+ await this._client.schema.alterTable("migration", (t) => {
201
+ t.string("version_new");
202
+ });
203
+ await this._client("migration").update({
204
+ version_new: this._client.raw("CAST(version AS TEXT)"),
205
+ });
206
+ await this._client.schema.alterTable("migration", (t) => {
207
+ t.dropColumn("version");
208
+ });
209
+ await this._client.schema.alterTable("migration", (t) => {
210
+ t.renameColumn("version_new", "version");
211
+ });
212
+ this.config !== false &&
213
+ this.config.logger?.log("Upgraded migration table: version column converted to string");
214
+ }
165
215
  }