@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/dist/index.js +100452 -11921
- package/package.json +5 -5
- package/src/drivers/index.ts +137 -12
- package/src/drivers/mysql.ts +91 -19
- package/src/drivers/postgres.ts +17 -12
- package/src/drivers/sqlite.ts +82 -16
- package/src/seeder.ts +22 -4
package/src/drivers/sqlite.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
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 {
|
|
4
|
+
import { getModelName, getTableName } from '@stacksjs/orm'
|
|
5
5
|
import { path } from '@stacksjs/path'
|
|
6
6
|
import { fs, glob } from '@stacksjs/storage'
|
|
7
|
+
import { snakeCase } from '@stacksjs/strings'
|
|
7
8
|
import type { Attribute, Attributes, Model } from '@stacksjs/types'
|
|
8
9
|
import {
|
|
10
|
+
arrangeColumns,
|
|
9
11
|
checkPivotMigration,
|
|
10
12
|
fetchOtherModelRelations,
|
|
13
|
+
findDifferingKeys,
|
|
11
14
|
getLastMigrationFields,
|
|
12
15
|
getPivotTables,
|
|
13
16
|
hasTableBeenMigrated,
|
|
17
|
+
isArrayEqual,
|
|
14
18
|
mapFieldTypeToColumnType,
|
|
19
|
+
pluckChanges,
|
|
15
20
|
} from '.'
|
|
16
21
|
|
|
17
22
|
export async function resetSqliteDatabase() {
|
|
@@ -123,7 +128,6 @@ async function createTableMigration(modelPath: string): Promise<void> {
|
|
|
123
128
|
|
|
124
129
|
await createPivotTableMigration(model, modelPath)
|
|
125
130
|
const otherModelRelations = await fetchOtherModelRelations(model, modelPath)
|
|
126
|
-
const fields = model.attributes
|
|
127
131
|
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
|
|
128
132
|
const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
|
|
129
133
|
|
|
@@ -134,16 +138,16 @@ async function createTableMigration(modelPath: string): Promise<void> {
|
|
|
134
138
|
migrationContent += ` .createTable('${tableName}')\n`
|
|
135
139
|
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())\n`
|
|
136
140
|
|
|
137
|
-
for (const [fieldName, options] of
|
|
141
|
+
for (const [fieldName, options] of arrangeColumns(model.attributes)) {
|
|
138
142
|
const fieldOptions = options as Attribute
|
|
139
|
-
const columnType = mapFieldTypeToColumnType(fieldOptions.
|
|
143
|
+
const columnType = mapFieldTypeToColumnType(fieldOptions.validations?.rule)
|
|
140
144
|
migrationContent += ` .addColumn('${fieldName}', '${columnType}'`
|
|
141
145
|
|
|
142
146
|
// Check if there are configurations that require the lambda function
|
|
143
|
-
if (fieldOptions.unique || fieldOptions.
|
|
147
|
+
if (fieldOptions.unique || fieldOptions.validations?.rule?.required) {
|
|
144
148
|
migrationContent += `, col => col`
|
|
145
149
|
if (fieldOptions.unique) migrationContent += `.unique()`
|
|
146
|
-
if (fieldOptions.
|
|
150
|
+
if (fieldOptions.validations?.rule?.required) migrationContent += `.notNull()`
|
|
147
151
|
migrationContent += ``
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -217,41 +221,103 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
217
221
|
const model = (await import(modelPath)).default as Model
|
|
218
222
|
const modelName = getModelName(model, modelPath)
|
|
219
223
|
const tableName = await getTableName(model, modelPath)
|
|
220
|
-
|
|
224
|
+
let hasChanged = false
|
|
221
225
|
// Assuming you have a function to get the fields from the last migration
|
|
222
226
|
// For simplicity, this is not implemented here
|
|
223
227
|
const lastMigrationFields = await getLastMigrationFields(modelName)
|
|
224
228
|
const lastFields = lastMigrationFields ?? {}
|
|
225
229
|
const currentFields = model.attributes as Attributes
|
|
226
230
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
231
|
+
const changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields))
|
|
232
|
+
|
|
233
|
+
const fieldsToAdd = changes?.added || []
|
|
234
|
+
|
|
235
|
+
const fieldsToRemove = changes?.removed || []
|
|
230
236
|
|
|
231
237
|
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
232
238
|
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
233
239
|
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
234
240
|
migrationContent += ` await db.schema.alterTable('${tableName}')\n`
|
|
235
241
|
|
|
242
|
+
if (fieldsToAdd.length || fieldsToRemove.length) {
|
|
243
|
+
hasChanged = true
|
|
244
|
+
migrationContent += ` await db.schema.alterTable('${tableName}')\n`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const fieldValidations = findDifferingKeys(lastFields, currentFields)
|
|
248
|
+
|
|
249
|
+
for (const fieldValidation of fieldValidations) {
|
|
250
|
+
hasChanged = true
|
|
251
|
+
const fieldNameFormatted = snakeCase(fieldValidation.key)
|
|
252
|
+
migrationContent += `await sql\`
|
|
253
|
+
ALTER TABLE ${tableName}
|
|
254
|
+
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(${fieldValidation.max})
|
|
255
|
+
\`.execute(db)\n\n`
|
|
256
|
+
}
|
|
257
|
+
|
|
236
258
|
// Add new fields
|
|
237
259
|
for (const fieldName of fieldsToAdd) {
|
|
238
260
|
const options = currentFields[fieldName] as Attribute
|
|
239
|
-
const columnType = mapFieldTypeToColumnType(options.
|
|
240
|
-
|
|
241
|
-
}
|
|
261
|
+
const columnType = mapFieldTypeToColumnType(options.validation?.rule)
|
|
262
|
+
const formattedFieldName = snakeCase(fieldName)
|
|
242
263
|
|
|
264
|
+
migrationContent += ` .addColumn('${formattedFieldName}', ${columnType}`
|
|
265
|
+
|
|
266
|
+
// Check if there are configurations that require the lambda function
|
|
267
|
+
if (options.unique || options?.required) {
|
|
268
|
+
migrationContent += `, col => col`
|
|
269
|
+
if (options.unique) migrationContent += `.unique()`
|
|
270
|
+
if (options?.required) migrationContent += `.notNull()`
|
|
271
|
+
migrationContent += ``
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
migrationContent += `)\n\n`
|
|
275
|
+
}
|
|
243
276
|
// Remove fields that no longer exist
|
|
244
277
|
for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
|
|
245
278
|
|
|
279
|
+
if (fieldsToAdd.length || fieldsToRemove.length) migrationContent += ` .execute();\n`
|
|
280
|
+
|
|
281
|
+
const lastFieldOrder = Object.values(lastFields).map((attr) => attr.order)
|
|
282
|
+
const currentFieldOrder = Object.values(currentFields).map((attr) => attr.order)
|
|
283
|
+
|
|
284
|
+
if (!isArrayEqual(lastFieldOrder, currentFieldOrder)) {
|
|
285
|
+
hasChanged = true
|
|
286
|
+
migrationContent += reArrangeColumns(model.attributes, tableName)
|
|
287
|
+
}
|
|
288
|
+
|
|
246
289
|
migrationContent += ` .execute();\n`
|
|
290
|
+
|
|
247
291
|
migrationContent += `}\n`
|
|
248
292
|
|
|
249
293
|
const timestamp = new Date().getTime().toString()
|
|
250
294
|
const migrationFileName = `${timestamp}-update-${tableName}-table.ts`
|
|
251
295
|
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
252
296
|
|
|
253
|
-
|
|
254
|
-
|
|
297
|
+
if (hasChanged) {
|
|
298
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
255
299
|
|
|
256
|
-
|
|
300
|
+
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function reArrangeColumns(attributes: Attributes | undefined, tableName: string): string {
|
|
304
|
+
const fields = arrangeColumns(attributes)
|
|
305
|
+
let migrationContent = ''
|
|
306
|
+
|
|
307
|
+
let previousField = ''
|
|
308
|
+
for (const [fieldName, options] of fields) {
|
|
309
|
+
const fieldNameFormatted = snakeCase(fieldName)
|
|
310
|
+
|
|
311
|
+
if (previousField) {
|
|
312
|
+
migrationContent += `await sql\`
|
|
313
|
+
ALTER TABLE ${tableName}
|
|
314
|
+
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
|
|
315
|
+
\`.execute(db)\n\n`
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
previousField = fieldNameFormatted
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return migrationContent
|
|
322
|
+
}
|
|
257
323
|
}
|
package/src/seeder.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { db } from '@stacksjs/database'
|
|
|
3
3
|
import { modelTableName } from '@stacksjs/orm'
|
|
4
4
|
import { path } from '@stacksjs/path'
|
|
5
5
|
import { fs, glob } from '@stacksjs/storage'
|
|
6
|
-
import { snakeCase } from '@stacksjs/strings'
|
|
6
|
+
import { singular, snakeCase } from '@stacksjs/strings'
|
|
7
7
|
import type { Model, RelationConfig } from '@stacksjs/types'
|
|
8
8
|
import { isString } from '@stacksjs/validation'
|
|
9
9
|
import { generateMigrations, resetDatabase, runDatabaseMigration } from './migrations'
|
|
@@ -31,9 +31,11 @@ async function seedModel(name: string, model?: Model) {
|
|
|
31
31
|
const record: any = {}
|
|
32
32
|
|
|
33
33
|
for (const fieldName in model.attributes) {
|
|
34
|
+
const formattedFieldName = snakeCase(fieldName)
|
|
34
35
|
const field = model.attributes[fieldName]
|
|
36
|
+
|
|
35
37
|
// Use the factory function if available, otherwise leave the field undefined
|
|
36
|
-
record[
|
|
38
|
+
record[formattedFieldName] = field?.factory ? field.factory() : undefined
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
if (otherRelations?.length) {
|
|
@@ -60,9 +62,10 @@ async function seedModelRelation(modelName: string): Promise<BigInt | number> {
|
|
|
60
62
|
const table = modelInstance.table
|
|
61
63
|
|
|
62
64
|
for (const fieldName in modelInstance.attributes) {
|
|
65
|
+
const formattedFieldName = snakeCase(fieldName)
|
|
63
66
|
const field = modelInstance.attributes[fieldName]
|
|
64
67
|
// Use the factory function if available, otherwise leave the field undefined
|
|
65
|
-
record[
|
|
68
|
+
record[formattedFieldName] = field?.factory ? field.factory() : undefined
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
const data = await db.insertInto(table).values(record).executeTakeFirstOrThrow()
|
|
@@ -97,7 +100,7 @@ export async function getRelations(model: Model): Promise<RelationConfig[]> {
|
|
|
97
100
|
relationName: relationInstance.relationName || '',
|
|
98
101
|
throughModel: relationInstance.through || '',
|
|
99
102
|
throughForeignKey: relationInstance.throughForeignKey || '',
|
|
100
|
-
pivotTable: relationInstance?.pivotTable ||
|
|
103
|
+
pivotTable: relationInstance?.pivotTable || getPivotTableName(formattedModelName || '', modelRelation.table),
|
|
101
104
|
})
|
|
102
105
|
}
|
|
103
106
|
}
|
|
@@ -106,6 +109,21 @@ export async function getRelations(model: Model): Promise<RelationConfig[]> {
|
|
|
106
109
|
return relationships
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
function getPivotTableName(formattedModelName: string, modelRelationTable: string): string {
|
|
113
|
+
// Create an array of the model names
|
|
114
|
+
const models = [formattedModelName, modelRelationTable]
|
|
115
|
+
|
|
116
|
+
// Sort the array alphabetically
|
|
117
|
+
models.sort()
|
|
118
|
+
|
|
119
|
+
models[0] = singular(models[0] || '')
|
|
120
|
+
|
|
121
|
+
// Join the sorted array with an underscore
|
|
122
|
+
const pivotTableName = models.join('_')
|
|
123
|
+
|
|
124
|
+
return pivotTableName
|
|
125
|
+
}
|
|
126
|
+
|
|
109
127
|
export async function fetchOtherModelRelations(model: Model): Promise<RelationConfig[]> {
|
|
110
128
|
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
111
129
|
const modelRelations = []
|