@stacksjs/database 0.61.24 → 0.63.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.63.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,17 +68,17 @@
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.11.0"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@stacksjs/development": "latest",
78
78
  "@types/tar": "^6.1.13",
79
- "debug": "^4.3.5",
79
+ "debug": "^4.3.6",
80
80
  "mkdirp": "^3.0.1",
81
81
  "q": "^1.5.1",
82
- "tar": "^7.2.0"
82
+ "tar": "^7.4.3"
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
- const modelName = getTableName(model, modelPath)
243
+ const tableName = getTableName(model, modelPath)
244
+
245
+ let firstForeignKey = ''
246
+ let secondForeignKey = ''
247
+ let table = ''
248
+ let modelRelationPath = ''
212
249
 
213
250
  if (model.belongsToMany) {
214
251
  if ('belongsToMany' in model) {
215
252
  for (const belongsToManyRelation of model.belongsToMany) {
216
- const modelRelationPath = path.userModelsPath(`${belongsToManyRelation}.ts`)
253
+ if (typeof belongsToManyRelation === 'string') {
254
+ modelRelationPath = path.userModelsPath(`${belongsToManyRelation}.ts`)
255
+ } else {
256
+ modelRelationPath = path.userModelsPath(`${belongsToManyRelation.model}.ts`)
257
+ }
258
+
217
259
  const modelRelation = (await import(modelRelationPath)).default
218
- const formattedModelName = modelName.toLowerCase()
260
+ const formattedTableName = tableName.toLowerCase()
219
261
 
220
262
  const modelRelationTable = getTableName(modelRelation, modelRelationPath)
221
- const modelRelationModelName = getModelName(modelRelation, modelRelationPath)
222
-
223
- const firstForeignKey =
224
- belongsToManyRelation.firstForeignKey || `${modelName.toLowerCase()}_${model.primaryKey}`
225
- const secondForeignKey =
226
- belongsToManyRelation.secondForeignKey || `${modelRelationModelName.toLowerCase()}_${model.primaryKey}`
263
+ // const modelRelationModelName = getModelName(modelRelation, modelRelationPath)
264
+
265
+ if (typeof belongsToManyRelation === 'string') {
266
+ firstForeignKey = `${singular(tableName.toLowerCase())}_${model.primaryKey}`
267
+ secondForeignKey = `${singular(modelRelationTable)}_${model.primaryKey}`
268
+ table = getPivotTableName(formattedTableName, modelRelationTable)
269
+ } else {
270
+ firstForeignKey =
271
+ belongsToManyRelation.firstForeignKey || `${singular(tableName.toLowerCase())}_${model.primaryKey}`
272
+ secondForeignKey =
273
+ belongsToManyRelation.secondForeignKey || `${singular(modelRelationTable)}_${model.primaryKey}`
274
+ table = belongsToManyRelation?.pivotTable || getPivotTableName(formattedTableName, 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,83 @@ 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
+ models[1] = plural(models[1] || '')
300
+
301
+ // Join the sorted array with an underscore
302
+ const pivotTableName = models.join('_')
303
+
304
+ return pivotTableName
305
+ }
306
+
242
307
  function hasRelations(obj: any, key: string): boolean {
243
308
  return key in obj
244
309
  }
310
+
311
+ export function pluckChanges(array1: string[], array2: string[]): { added: string[]; removed: string[] } | null {
312
+ const removed = array1.filter((item) => !array2.includes(item))
313
+ const added = array2.filter((item) => !array1.includes(item))
314
+
315
+ if (removed.length === 0 && added.length === 0) {
316
+ return null
317
+ }
318
+
319
+ return { added, removed }
320
+ }
321
+
322
+ export function arrangeColumns(attributes: Attributes | undefined) {
323
+ if (!attributes) return []
324
+
325
+ const entries = Object.entries(attributes)
326
+
327
+ entries.sort(([keyA, valueA], [keyB, valueB]) => {
328
+ const orderA = valueA.order ?? Number.POSITIVE_INFINITY
329
+ const orderB = valueB.order ?? Number.POSITIVE_INFINITY
330
+ return orderA - orderB
331
+ })
332
+
333
+ return entries
334
+ }
335
+
336
+ export function isArrayEqual(arr1: (number | undefined)[], arr2: (number | undefined)[]): boolean {
337
+ if (!arr1 || !arr2) {
338
+ return false
339
+ }
340
+
341
+ if (arr1.length !== arr2.length) return false
342
+
343
+ for (let i = 0; i < arr1.length; i++) {
344
+ if (arr1[i] !== arr2[i]) return false
345
+ }
346
+
347
+ return true
348
+ }
349
+
350
+ export function findDifferingKeys(obj1: any, obj2: any): { key: string; max: number; min: number }[] {
351
+ const differingKeys: { key: string; max: number; min: number }[] = []
352
+
353
+ for (const key in obj1) {
354
+ if (Object.prototype.hasOwnProperty.call(obj1, key) && Object.prototype.hasOwnProperty.call(obj2, key)) {
355
+ const lastCharacterLength = findCharacterLength(obj1[key].validation.rule)
356
+ const latestCharacterLength = findCharacterLength(obj2[key].validation.rule)
357
+
358
+ if (lastCharacterLength !== undefined && latestCharacterLength !== undefined) {
359
+ if (
360
+ lastCharacterLength.max !== latestCharacterLength.max ||
361
+ lastCharacterLength.min !== latestCharacterLength.min
362
+ ) {
363
+ differingKeys.push({ key, max: latestCharacterLength.max, min: latestCharacterLength.min })
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ return differingKeys
370
+ }
@@ -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()
@@ -61,20 +67,20 @@ export async function generateMysqlMigration(modelPath: string) {
61
67
  // check if any files are in the database folder
62
68
  const files = await fs.readdir(path.userMigrationsPath())
63
69
 
64
- if (files.length === 0) {
65
- log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
70
+ // if (files.length === 0) {
71
+ // log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
66
72
 
67
- // delete the *.ts files in the database/models folder
68
- const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
73
+ // // delete the *.ts files in the database/models folder
74
+ // const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
69
75
 
70
- if (modelFiles.length) {
71
- log.debug('No existing model files in framework path...')
76
+ // if (modelFiles.length) {
77
+ // log.debug('No existing model files in framework path...')
72
78
 
73
- for (const file of modelFiles) {
74
- if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
75
- }
76
- }
77
- }
79
+ // for (const file of modelFiles) {
80
+ // if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
81
+ // }
82
+ // }
83
+ // }
78
84
 
79
85
  const model = (await import(modelPath)).default as Model
80
86
  const fileName = path.basename(modelPath)
@@ -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) =>
@@ -166,7 +177,7 @@ async function createTableMigration(modelPath: string) {
166
177
  // Append created_at and updated_at columns if useTimestamps is true
167
178
  if (useTimestamps) {
168
179
  migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
169
- migrationContent += ` .addColumn('updated_at', 'timestamp')\n`
180
+ migrationContent += ` .addColumn('updated_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
170
181
  }
171
182
 
172
183
  // Append deleted_at column if useSoftDeletes is true
@@ -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