@stacksjs/database 0.61.24 → 0.62.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
- "version": "0.61.24",
4
+ "version": "0.62.0",
5
5
  "description": "The Stacks database integration.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -57,7 +57,7 @@
57
57
  "@stacksjs/storage": "latest",
58
58
  "@stacksjs/strings": "latest",
59
59
  "@stacksjs/utils": "latest",
60
- "kysely-bun-worker": "^0.6.1"
60
+ "kysely-bun-worker": "^0.6.2"
61
61
  },
62
62
  "dependencies": {
63
63
  "@stacksjs/cli": "latest",
@@ -68,10 +68,10 @@
68
68
  "@stacksjs/storage": "latest",
69
69
  "@stacksjs/strings": "latest",
70
70
  "@stacksjs/utils": "latest",
71
- "kysely-bun-worker": "^0.6.1"
71
+ "kysely-bun-worker": "^0.6.2"
72
72
  },
73
73
  "optionalDependencies": {
74
- "mysql2": "^3.10.0"
74
+ "mysql2": "^3.10.2"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@stacksjs/development": "latest",
@@ -79,6 +79,6 @@
79
79
  "debug": "^4.3.5",
80
80
  "mkdirp": "^3.0.1",
81
81
  "q": "^1.5.1",
82
- "tar": "^7.2.0"
82
+ "tar": "^7.4.0"
83
83
  }
84
84
  }
@@ -3,7 +3,7 @@ import { db } from '@stacksjs/database'
3
3
  import { getModelName, getTableName } from '@stacksjs/orm'
4
4
  import { path } from '@stacksjs/path'
5
5
  import { fs, glob } from '@stacksjs/storage'
6
- import { plural, snakeCase } from '@stacksjs/strings'
6
+ import { plural, singular, snakeCase } from '@stacksjs/strings'
7
7
  import type { Attributes, Model, RelationConfig, VineType } from '@stacksjs/types'
8
8
  import { isString } from '@stacksjs/validation'
9
9
 
@@ -11,6 +11,11 @@ export * from './mysql'
11
11
  export * from './postgres'
12
12
  export * from './sqlite'
13
13
 
14
+ interface Range {
15
+ min: number
16
+ max: number
17
+ }
18
+
14
19
  export async function getLastMigrationFields(modelName: string): Promise<Attributes> {
15
20
  const oldModelPath = path.frameworkPath(`database/models/${modelName}`)
16
21
  const model = (await import(oldModelPath)).default as Model
@@ -116,6 +121,34 @@ export function prepareTextColumnType(rule: VineType) {
116
121
  return `'${columnType}'`
117
122
  }
118
123
 
124
+ export function findCharacterLength(rule: VineType): { min: number; max: number } | undefined {
125
+ const result: any = {}
126
+
127
+ // Find min and max length validations
128
+ const minLengthValidation = rule.validations.find((v: any) => v.options?.min !== undefined)
129
+ const maxLengthValidation = rule.validations.find((v: any) => v.options?.max !== undefined)
130
+
131
+ if (minLengthValidation === undefined || maxLengthValidation === undefined) {
132
+ return undefined
133
+ }
134
+
135
+ for (const key of ['min', 'max']) {
136
+ if (maxLengthValidation.options[key] === undefined && minLengthValidation.options[key] === undefined) continue
137
+
138
+ result.max = maxLengthValidation.options[key]
139
+ result.min = minLengthValidation.options[key]
140
+ }
141
+
142
+ // if (minLengthValidation.options[key] !== maxLengthValidation.options[key]) {
143
+ // result[key] = maxLengthValidation.options[key];
144
+ // }
145
+ return result
146
+ }
147
+
148
+ export function compareRanges(range1: Range, range2: Range): boolean {
149
+ return range1.min === range2.min && range1.max === range2.max
150
+ }
151
+
119
152
  export async function checkPivotMigration(dynamicPart: string): Promise<boolean> {
120
153
  const files = await fs.readdir(path.userMigrationsPath())
121
154
 
@@ -147,7 +180,6 @@ export async function getRelations(model: Model, modelPath: string): Promise<Rel
147
180
  const modelRelationPath = path.userModelsPath(`${relationModel}.ts`)
148
181
  const modelRelation = (await import(modelRelationPath)).default
149
182
 
150
-
151
183
  const modelName = getModelName(model, modelPath)
152
184
  const formattedModelName = modelName.toLowerCase()
153
185
  const tableName = getTableName(model, modelPath)
@@ -164,7 +196,7 @@ export async function getRelations(model: Model, modelPath: string): Promise<Rel
164
196
  relationName: relationInstance.relationName || '',
165
197
  throughModel: relationInstance.through || '',
166
198
  throughForeignKey: relationInstance.throughForeignKey || '',
167
- pivotTable: relationInstance?.pivotTable || `${formattedModelName}_${modelRelationTable}`,
199
+ pivotTable: relationInstance?.pivotTable || getPivotTableName(formattedModelName, modelRelationTable),
168
200
  })
169
201
  }
170
202
  }
@@ -204,31 +236,48 @@ export async function fetchOtherModelRelations(model: Model, modelPath: string):
204
236
 
205
237
  export async function getPivotTables(
206
238
  model: Model,
207
- modelPath: string
239
+ modelPath: string,
208
240
  ): Promise<{ table: string; firstForeignKey: string | undefined; secondForeignKey: string | undefined }[]> {
209
241
  const pivotTable = []
210
242
 
211
243
  const modelName = getTableName(model, modelPath)
244
+ let firstForeignKey = ''
245
+ let secondForeignKey = ''
246
+ let table = ''
247
+ let modelRelationPath = ''
212
248
 
213
249
  if (model.belongsToMany) {
214
250
  if ('belongsToMany' in model) {
215
251
  for (const belongsToManyRelation of model.belongsToMany) {
216
- const modelRelationPath = path.userModelsPath(`${belongsToManyRelation}.ts`)
252
+ if (typeof belongsToManyRelation === 'string') {
253
+ modelRelationPath = path.userModelsPath(`${belongsToManyRelation}.ts`)
254
+ } else {
255
+ modelRelationPath = path.userModelsPath(`${belongsToManyRelation.model}.ts`)
256
+ }
257
+
217
258
  const modelRelation = (await import(modelRelationPath)).default
218
259
  const formattedModelName = modelName.toLowerCase()
219
260
 
220
261
  const modelRelationTable = getTableName(modelRelation, modelRelationPath)
221
262
  const modelRelationModelName = getModelName(modelRelation, modelRelationPath)
222
263
 
223
- const firstForeignKey =
224
- belongsToManyRelation.firstForeignKey || `${modelName.toLowerCase()}_${model.primaryKey}`
225
- const secondForeignKey =
226
- belongsToManyRelation.secondForeignKey || `${modelRelationModelName.toLowerCase()}_${model.primaryKey}`
264
+ if (typeof belongsToManyRelation === 'string') {
265
+ firstForeignKey = `${singular(modelName.toLowerCase())}_${model.primaryKey}`
266
+ secondForeignKey = `${singular(modelRelationModelName.toLowerCase())}_${model.primaryKey}`
267
+ table = getPivotTableName(formattedModelName, modelRelationTable)
268
+ } else {
269
+ firstForeignKey =
270
+ belongsToManyRelation.firstForeignKey || `${singular(modelName.toLowerCase())}_${model.primaryKey}`
271
+ secondForeignKey =
272
+ belongsToManyRelation.secondForeignKey ||
273
+ `${singular(modelRelationModelName.toLowerCase())}_${model.primaryKey}`
274
+ table = belongsToManyRelation?.pivotTable || getPivotTableName(formattedModelName, modelRelationTable)
275
+ }
227
276
 
228
277
  pivotTable.push({
229
- table: belongsToManyRelation?.pivotTable || `${formattedModelName}_${modelRelationTable}`,
230
- firstForeignKey: firstForeignKey,
231
- secondForeignKey: secondForeignKey,
278
+ table,
279
+ firstForeignKey,
280
+ secondForeignKey,
232
281
  })
233
282
  }
234
283
 
@@ -239,6 +288,82 @@ export async function getPivotTables(
239
288
  return []
240
289
  }
241
290
 
291
+ function getPivotTableName(formattedModelName: string, modelRelationTable: string): string {
292
+ // Create an array of the model names
293
+ const models = [formattedModelName, modelRelationTable]
294
+
295
+ // Sort the array alphabetically
296
+ models.sort()
297
+
298
+ models[0] = singular(models[0] || '')
299
+
300
+ // Join the sorted array with an underscore
301
+ const pivotTableName = models.join('_')
302
+
303
+ return pivotTableName
304
+ }
305
+
242
306
  function hasRelations(obj: any, key: string): boolean {
243
307
  return key in obj
244
308
  }
309
+
310
+ export function pluckChanges(array1: string[], array2: string[]): { added: string[]; removed: string[] } | null {
311
+ const removed = array1.filter((item) => !array2.includes(item))
312
+ const added = array2.filter((item) => !array1.includes(item))
313
+
314
+ if (removed.length === 0 && added.length === 0) {
315
+ return null
316
+ }
317
+
318
+ return { added, removed }
319
+ }
320
+
321
+ export function arrangeColumns(attributes: Attributes | undefined) {
322
+ if (!attributes) return []
323
+
324
+ const entries = Object.entries(attributes)
325
+
326
+ entries.sort(([keyA, valueA], [keyB, valueB]) => {
327
+ const orderA = valueA.order ?? Number.POSITIVE_INFINITY
328
+ const orderB = valueB.order ?? Number.POSITIVE_INFINITY
329
+ return orderA - orderB
330
+ })
331
+
332
+ return entries
333
+ }
334
+
335
+ export function isArrayEqual(arr1: (number | undefined)[], arr2: (number | undefined)[]): boolean {
336
+ if (!arr1 || !arr2) {
337
+ return false
338
+ }
339
+
340
+ if (arr1.length !== arr2.length) return false
341
+
342
+ for (let i = 0; i < arr1.length; i++) {
343
+ if (arr1[i] !== arr2[i]) return false
344
+ }
345
+
346
+ return true
347
+ }
348
+
349
+ export function findDifferingKeys(obj1: any, obj2: any): { key: string; max: number; min: number }[] {
350
+ const differingKeys: { key: string; max: number; min: number }[] = []
351
+
352
+ for (const key in obj1) {
353
+ if (obj1.hasOwnProperty(key) && obj2.hasOwnProperty(key)) {
354
+ const lastCharacterLength = findCharacterLength(obj1[key].validation.rule)
355
+ const latestCharacterLength = findCharacterLength(obj2[key].validation.rule)
356
+
357
+ if (lastCharacterLength !== undefined && latestCharacterLength !== undefined) {
358
+ if (
359
+ lastCharacterLength.max !== latestCharacterLength.max ||
360
+ lastCharacterLength.min !== latestCharacterLength.min
361
+ ) {
362
+ differingKeys.push({ key, max: latestCharacterLength.max, min: latestCharacterLength.min })
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ return differingKeys
369
+ }
@@ -1,18 +1,24 @@
1
1
  import { italic, log } from '@stacksjs/cli'
2
2
  import { db } from '@stacksjs/database'
3
3
  import { ok } from '@stacksjs/error-handling'
4
+ import { getModelName, getTableName } from '@stacksjs/orm'
4
5
  import { path } from '@stacksjs/path'
5
6
  import { fs, glob } from '@stacksjs/storage'
7
+ import { snakeCase } from '@stacksjs/strings'
6
8
  import type { Attribute, Attributes, Model } from '@stacksjs/types'
7
9
  import {
10
+ arrangeColumns,
8
11
  checkPivotMigration,
9
12
  fetchOtherModelRelations,
13
+ findCharacterLength,
14
+ findDifferingKeys,
10
15
  getLastMigrationFields,
11
16
  getPivotTables,
12
17
  hasTableBeenMigrated,
18
+ isArrayEqual,
13
19
  mapFieldTypeToColumnType,
20
+ pluckChanges,
14
21
  } from '.'
15
- import { getModelName, getTableName } from '@stacksjs/orm'
16
22
 
17
23
  export async function resetMysqlDatabase() {
18
24
  const tables = await fetchMysqlTables()
@@ -124,11 +130,11 @@ async function createTableMigration(modelPath: string) {
124
130
  const model = (await import(modelPath)).default as Model
125
131
  const tableName = await getTableName(model, modelPath)
126
132
 
127
- await createPivotTableMigration(model, modelPath)
133
+ const twoFactorEnabled = model.traits?.useAuth?.useTwoFactor
128
134
 
135
+ await createPivotTableMigration(model, modelPath)
129
136
  const otherModelRelations = await fetchOtherModelRelations(model, modelPath)
130
137
 
131
- const fields = model.attributes
132
138
  const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
133
139
  const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
134
140
 
@@ -139,22 +145,27 @@ async function createTableMigration(modelPath: string) {
139
145
  migrationContent += ` .createTable('${tableName}')\n`
140
146
  migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())\n`
141
147
 
142
- for (const [fieldName, options] of Object.entries(fields)) {
148
+ for (const [fieldName, options] of arrangeColumns(model.attributes)) {
143
149
  const fieldOptions = options as Attribute
144
- const columnType = mapFieldTypeToColumnType(fieldOptions.validator?.rule)
145
- migrationContent += ` .addColumn('${fieldName}', ${columnType}`
150
+ const fieldNameFormatted = snakeCase(fieldName)
151
+ const columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule)
152
+ migrationContent += ` .addColumn('${fieldNameFormatted}', ${columnType}`
146
153
 
147
154
  // Check if there are configurations that require the lambda function
148
- if (fieldOptions.unique || fieldOptions.validator?.rule?.required) {
155
+ if (fieldOptions.unique || fieldOptions?.required) {
149
156
  migrationContent += `, col => col`
150
157
  if (fieldOptions.unique) migrationContent += `.unique()`
151
- if (fieldOptions.validator?.rule?.required) migrationContent += `.notNull()`
158
+ if (fieldOptions?.required) migrationContent += `.notNull()`
152
159
  migrationContent += ``
153
160
  }
154
161
 
155
162
  migrationContent += `)\n`
156
163
  }
157
164
 
165
+ if (false !== twoFactorEnabled && twoFactorEnabled) {
166
+ migrationContent += ` .addColumn('two_factor_secret', 'varchar(255)')\n`
167
+ }
168
+
158
169
  if (otherModelRelations?.length) {
159
170
  for (const modelRelation of otherModelRelations) {
160
171
  migrationContent += ` .addColumn('${modelRelation.foreignKey}', 'integer', (col) =>
@@ -179,7 +190,6 @@ async function createTableMigration(modelPath: string) {
179
190
  const migrationFileName = `${timestamp}-create-${tableName}-table.ts`
180
191
  const migrationFilePath = path.userMigrationsPath(migrationFileName)
181
192
 
182
- // Assuming fs.writeFileSync is available or use an equivalent method
183
193
  Bun.write(migrationFilePath, migrationContent)
184
194
 
185
195
  log.success(`Created migration: ${italic(migrationFileName)}`)
@@ -209,7 +219,6 @@ async function createPivotTableMigration(model: Model, modelPath: string) {
209
219
  const migrationFileName = `${timestamp}-create-${pivotTable.table}-table.ts`
210
220
  const migrationFilePath = path.userMigrationsPath(migrationFileName)
211
221
 
212
- // Assuming fs.writeFileSync is available or use an equivalent method
213
222
  Bun.write(migrationFilePath, migrationContent)
214
223
 
215
224
  log.success(`Created pivot migration: ${migrationFileName}`)
@@ -222,6 +231,7 @@ export async function createAlterTableMigration(modelPath: string) {
222
231
  const model = (await import(modelPath)).default as Model
223
232
  const modelName = getModelName(model, modelPath)
224
233
  const tableName = await getTableName(model, modelPath)
234
+ let hasChanged = false
225
235
 
226
236
  // Assuming you have a function to get the fields from the last migration
227
237
  // For simplicity, this is not implemented here
@@ -230,35 +240,97 @@ export async function createAlterTableMigration(modelPath: string) {
230
240
  const currentFields = model.attributes as Attributes
231
241
 
232
242
  // Determine fields to add and remove
233
- const fieldsToAdd = Object.keys(currentFields)
234
- const fieldsToRemove = Object.keys(lastFields)
243
+
244
+ const changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields))
245
+
246
+ const fieldsToAdd = changes?.added || []
247
+
248
+ const fieldsToRemove = changes?.removed || []
235
249
 
236
250
  let migrationContent = `import type { Database } from '@stacksjs/database'\n`
237
251
  migrationContent += `import { sql } from '@stacksjs/database'\n\n`
238
252
  migrationContent += `export async function up(db: Database<any>) {\n`
239
- migrationContent += ` await db.schema.alterTable('${tableName}')\n`
253
+
254
+ if (fieldsToAdd.length || fieldsToRemove.length) {
255
+ hasChanged = true
256
+ migrationContent += ` await db.schema.alterTable('${tableName}')\n`
257
+ }
258
+
259
+ const fieldValidations = findDifferingKeys(lastFields, currentFields)
260
+
261
+ for (const fieldValidation of fieldValidations) {
262
+ hasChanged = true
263
+ const fieldNameFormatted = snakeCase(fieldValidation.key)
264
+ migrationContent += `await sql\`
265
+ ALTER TABLE ${tableName}
266
+ MODIFY COLUMN ${fieldNameFormatted} VARCHAR(${fieldValidation.max})
267
+ \`.execute(db)\n\n`
268
+ }
240
269
 
241
270
  // Add new fields
242
271
  for (const fieldName of fieldsToAdd) {
243
272
  const options = currentFields[fieldName] as Attribute
244
- const columnType = mapFieldTypeToColumnType(options.validator?.rule)
245
- migrationContent += ` .addColumn('${fieldName}', '${columnType}')\n`
273
+ const columnType = mapFieldTypeToColumnType(options.validation?.rule)
274
+ const formattedFieldName = snakeCase(fieldName)
275
+
276
+ migrationContent += ` .addColumn('${formattedFieldName}', ${columnType}`
277
+
278
+ // Check if there are configurations that require the lambda function
279
+ if (options.unique || options?.required) {
280
+ migrationContent += `, col => col`
281
+ if (options.unique) migrationContent += `.unique()`
282
+ if (options?.required) migrationContent += `.notNull()`
283
+ migrationContent += ``
284
+ }
285
+
286
+ migrationContent += `)\n\n`
246
287
  }
247
288
 
248
289
  // Remove fields that no longer exist
249
290
  for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
250
291
 
251
- migrationContent += ` .execute();\n`
292
+ if (fieldsToAdd.length || fieldsToRemove.length) migrationContent += ` .execute();\n`
293
+
294
+ const lastFieldOrder = Object.values(lastFields).map((attr) => attr.order)
295
+ const currentFieldOrder = Object.values(currentFields).map((attr) => attr.order)
296
+
297
+ if (!isArrayEqual(lastFieldOrder, currentFieldOrder)) {
298
+ hasChanged = true
299
+ migrationContent += reArrangeColumns(model.attributes, tableName)
300
+ }
301
+
252
302
  migrationContent += `}\n`
253
303
 
254
304
  const timestamp = new Date().getTime().toString()
255
305
  const migrationFileName = `${timestamp}-update-${tableName}-table.ts`
256
306
  const migrationFilePath = path.userMigrationsPath(migrationFileName)
257
307
 
258
- // Assuming fs.writeFileSync is available or use an equivalent method
259
- Bun.write(migrationFilePath, migrationContent)
308
+ if (hasChanged) {
309
+ Bun.write(migrationFilePath, migrationContent)
260
310
 
261
- log.success(`Created migration: ${italic(migrationFileName)}`)
311
+ log.success(`Created migration: ${italic(migrationFileName)}`)
312
+ }
313
+ }
314
+
315
+ function reArrangeColumns(attributes: Attributes | undefined, tableName: string): string {
316
+ const fields = arrangeColumns(attributes)
317
+ let migrationContent = ''
318
+
319
+ let previousField = ''
320
+ for (const [fieldName, options] of fields) {
321
+ const fieldNameFormatted = snakeCase(fieldName)
322
+
323
+ if (previousField) {
324
+ migrationContent += `await sql\`
325
+ ALTER TABLE ${tableName}
326
+ MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
327
+ \`.execute(db)\n\n`
328
+ }
329
+
330
+ previousField = fieldNameFormatted
331
+ }
332
+
333
+ return migrationContent
262
334
  }
263
335
 
264
336
  export async function fetchMysqlTables(): Promise<string[]> {
@@ -4,14 +4,17 @@ import { ok } from '@stacksjs/error-handling'
4
4
  import { getTableName } from '@stacksjs/orm'
5
5
  import { path } from '@stacksjs/path'
6
6
  import { fs, glob } from '@stacksjs/storage'
7
- import type { Attribute, Model } from '@stacksjs/types'
7
+ import { snakeCase } from '@stacksjs/strings'
8
+ import type { Attribute, Attributes, Model } from '@stacksjs/types'
8
9
  import {
10
+ arrangeColumns,
9
11
  checkPivotMigration,
10
12
  fetchOtherModelRelations,
11
13
  getLastMigrationFields,
12
14
  getPivotTables,
13
15
  hasTableBeenMigrated,
14
16
  mapFieldTypeToColumnType,
17
+ pluckChanges,
15
18
  } from '.'
16
19
 
17
20
  export async function resetPostgresDatabase() {
@@ -128,7 +131,6 @@ async function createTableMigration(modelPath: string) {
128
131
  await createPivotTableMigration(model, modelPath)
129
132
 
130
133
  const otherModelRelations = await fetchOtherModelRelations(model, modelPath)
131
- const fields = model.attributes
132
134
  const useTimestamps = model.traits?.useTimestamps ?? model.traits?.timestampable ?? true
133
135
  const useSoftDeletes = model.traits?.useSoftDeletes ?? model.traits?.softDeletable ?? false
134
136
 
@@ -139,16 +141,17 @@ async function createTableMigration(modelPath: string) {
139
141
  migrationContent += ` .createTable('${tableName}')\n`
140
142
  migrationContent += ` .addColumn('id', 'serial', (col) => col.primaryKey())\n`
141
143
 
142
- for (const [fieldName, options] of Object.entries(fields)) {
144
+ for (const [fieldName, options] of arrangeColumns(model.attributes)) {
143
145
  const fieldOptions = options as Attribute
144
- const columnType = mapFieldTypeToColumnType(fieldOptions.validator?.rule)
145
- migrationContent += ` .addColumn('${fieldName}', '${columnType}'`
146
+ const fieldNameFormatted = snakeCase(fieldName)
147
+ const columnType = mapFieldTypeToColumnType(fieldOptions.validations?.rule)
148
+ migrationContent += ` .addColumn('${fieldNameFormatted}', '${columnType}'`
146
149
 
147
150
  // Check if there are configurations that require the lambda function
148
- if (fieldOptions.unique || fieldOptions.validator?.rule?.required) {
151
+ if (fieldOptions.unique || fieldOptions.validations?.rule?.required) {
149
152
  migrationContent += `, col => col`
150
153
  if (fieldOptions.unique) migrationContent += `.unique()`
151
- if (fieldOptions.validator?.rule?.required) migrationContent += `.notNull()`
154
+ if (fieldOptions.validations?.rule?.required) migrationContent += `.notNull()`
152
155
  migrationContent += ``
153
156
  }
154
157
 
@@ -227,11 +230,13 @@ export async function createAlterTableMigration(modelPath: string) {
227
230
  // For simplicity, this is not implemented here
228
231
  const lastMigrationFields = await getLastMigrationFields(modelName)
229
232
  const lastFields = lastMigrationFields ?? {}
230
- const currentFields = model.attributes
233
+ const currentFields = model.attributes as Attributes
231
234
 
232
- // Determine fields to add and remove
233
- const fieldsToAdd = Object.keys(currentFields)
234
- const fieldsToRemove = Object.keys(lastFields)
235
+ const changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields))
236
+
237
+ const fieldsToAdd = changes?.added || []
238
+
239
+ const fieldsToRemove = changes?.removed || []
235
240
 
236
241
  let migrationContent = `import type { Database } from '@stacksjs/database'\n`
237
242
  migrationContent += `import { sql } from '@stacksjs/database'\n\n`
@@ -241,7 +246,7 @@ export async function createAlterTableMigration(modelPath: string) {
241
246
  // Add new fields
242
247
  for (const fieldName of fieldsToAdd) {
243
248
  const options = currentFields[fieldName] as Attribute
244
- const columnType = mapFieldTypeToColumnType(options.validator?.rule)
249
+ const columnType = mapFieldTypeToColumnType(options.validations?.rule)
245
250
  migrationContent += ` .addColumn('${fieldName}', '${columnType}')\n`
246
251
  }
247
252