@stacksjs/database 0.62.0 → 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/dist/index.js +18751 -105245
- package/dist/index.js.map +307 -0
- package/package.json +4 -4
- package/src/drivers/index.ts +12 -11
- package/src/drivers/mysql.ts +12 -12
- package/src/drivers/sqlite.ts +33 -26
- package/src/migrations.ts +4 -3
- package/src/seeder.ts +84 -25
- package/src/utils.ts +2 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/database",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.63.0",
|
|
5
5
|
"description": "The Stacks database integration.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"license": "MIT",
|
|
@@ -71,14 +71,14 @@
|
|
|
71
71
|
"kysely-bun-worker": "^0.6.2"
|
|
72
72
|
},
|
|
73
73
|
"optionalDependencies": {
|
|
74
|
-
"mysql2": "^3.
|
|
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.
|
|
79
|
+
"debug": "^4.3.6",
|
|
80
80
|
"mkdirp": "^3.0.1",
|
|
81
81
|
"q": "^1.5.1",
|
|
82
|
-
"tar": "^7.4.
|
|
82
|
+
"tar": "^7.4.3"
|
|
83
83
|
}
|
|
84
84
|
}
|
package/src/drivers/index.ts
CHANGED
|
@@ -240,7 +240,8 @@ export async function getPivotTables(
|
|
|
240
240
|
): Promise<{ table: string; firstForeignKey: string | undefined; secondForeignKey: string | undefined }[]> {
|
|
241
241
|
const pivotTable = []
|
|
242
242
|
|
|
243
|
-
const
|
|
243
|
+
const tableName = getTableName(model, modelPath)
|
|
244
|
+
|
|
244
245
|
let firstForeignKey = ''
|
|
245
246
|
let secondForeignKey = ''
|
|
246
247
|
let table = ''
|
|
@@ -256,22 +257,21 @@ export async function getPivotTables(
|
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
const modelRelation = (await import(modelRelationPath)).default
|
|
259
|
-
const
|
|
260
|
+
const formattedTableName = tableName.toLowerCase()
|
|
260
261
|
|
|
261
262
|
const modelRelationTable = getTableName(modelRelation, modelRelationPath)
|
|
262
|
-
const modelRelationModelName = getModelName(modelRelation, modelRelationPath)
|
|
263
|
+
// const modelRelationModelName = getModelName(modelRelation, modelRelationPath)
|
|
263
264
|
|
|
264
265
|
if (typeof belongsToManyRelation === 'string') {
|
|
265
|
-
firstForeignKey = `${singular(
|
|
266
|
-
secondForeignKey = `${singular(
|
|
267
|
-
table = getPivotTableName(
|
|
266
|
+
firstForeignKey = `${singular(tableName.toLowerCase())}_${model.primaryKey}`
|
|
267
|
+
secondForeignKey = `${singular(modelRelationTable)}_${model.primaryKey}`
|
|
268
|
+
table = getPivotTableName(formattedTableName, modelRelationTable)
|
|
268
269
|
} else {
|
|
269
270
|
firstForeignKey =
|
|
270
|
-
belongsToManyRelation.firstForeignKey || `${singular(
|
|
271
|
+
belongsToManyRelation.firstForeignKey || `${singular(tableName.toLowerCase())}_${model.primaryKey}`
|
|
271
272
|
secondForeignKey =
|
|
272
|
-
belongsToManyRelation.secondForeignKey ||
|
|
273
|
-
|
|
274
|
-
table = belongsToManyRelation?.pivotTable || getPivotTableName(formattedModelName, modelRelationTable)
|
|
273
|
+
belongsToManyRelation.secondForeignKey || `${singular(modelRelationTable)}_${model.primaryKey}`
|
|
274
|
+
table = belongsToManyRelation?.pivotTable || getPivotTableName(formattedTableName, modelRelationTable)
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
pivotTable.push({
|
|
@@ -296,6 +296,7 @@ function getPivotTableName(formattedModelName: string, modelRelationTable: strin
|
|
|
296
296
|
models.sort()
|
|
297
297
|
|
|
298
298
|
models[0] = singular(models[0] || '')
|
|
299
|
+
models[1] = plural(models[1] || '')
|
|
299
300
|
|
|
300
301
|
// Join the sorted array with an underscore
|
|
301
302
|
const pivotTableName = models.join('_')
|
|
@@ -350,7 +351,7 @@ export function findDifferingKeys(obj1: any, obj2: any): { key: string; max: num
|
|
|
350
351
|
const differingKeys: { key: string; max: number; min: number }[] = []
|
|
351
352
|
|
|
352
353
|
for (const key in obj1) {
|
|
353
|
-
if (
|
|
354
|
+
if (Object.prototype.hasOwnProperty.call(obj1, key) && Object.prototype.hasOwnProperty.call(obj2, key)) {
|
|
354
355
|
const lastCharacterLength = findCharacterLength(obj1[key].validation.rule)
|
|
355
356
|
const latestCharacterLength = findCharacterLength(obj2[key].validation.rule)
|
|
356
357
|
|
package/src/drivers/mysql.ts
CHANGED
|
@@ -67,20 +67,20 @@ export async function generateMysqlMigration(modelPath: string) {
|
|
|
67
67
|
// check if any files are in the database folder
|
|
68
68
|
const files = await fs.readdir(path.userMigrationsPath())
|
|
69
69
|
|
|
70
|
-
if (files.length === 0) {
|
|
71
|
-
|
|
70
|
+
// if (files.length === 0) {
|
|
71
|
+
// log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
// // delete the *.ts files in the database/models folder
|
|
74
|
+
// const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// if (modelFiles.length) {
|
|
77
|
+
// log.debug('No existing model files in framework path...')
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
79
|
+
// for (const file of modelFiles) {
|
|
80
|
+
// if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
|
|
81
|
+
// }
|
|
82
|
+
// }
|
|
83
|
+
// }
|
|
84
84
|
|
|
85
85
|
const model = (await import(modelPath)).default as Model
|
|
86
86
|
const fileName = path.basename(modelPath)
|
|
@@ -177,7 +177,7 @@ async function createTableMigration(modelPath: string) {
|
|
|
177
177
|
// Append created_at and updated_at columns if useTimestamps is true
|
|
178
178
|
if (useTimestamps) {
|
|
179
179
|
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
|
|
180
|
-
migrationContent += ` .addColumn('updated_at', 'timestamp')\n`
|
|
180
|
+
migrationContent += ` .addColumn('updated_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// Append deleted_at column if useSoftDeletes is true
|
package/src/drivers/sqlite.ts
CHANGED
|
@@ -120,14 +120,17 @@ export async function generateSqliteMigration(modelPath: string) {
|
|
|
120
120
|
else await createTableMigration(modelPath)
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
async function createTableMigration(modelPath: string)
|
|
123
|
+
async function createTableMigration(modelPath: string) {
|
|
124
124
|
log.debug('createTableMigration modelPath:', modelPath)
|
|
125
125
|
|
|
126
126
|
const model = (await import(modelPath)).default as Model
|
|
127
127
|
const tableName = await getTableName(model, modelPath)
|
|
128
128
|
|
|
129
|
+
const twoFactorEnabled = model.traits?.useAuth?.useTwoFactor
|
|
130
|
+
|
|
129
131
|
await createPivotTableMigration(model, modelPath)
|
|
130
132
|
const otherModelRelations = await fetchOtherModelRelations(model, modelPath)
|
|
133
|
+
|
|
131
134
|
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
|
|
132
135
|
const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
|
|
133
136
|
|
|
@@ -140,24 +143,29 @@ async function createTableMigration(modelPath: string): Promise<void> {
|
|
|
140
143
|
|
|
141
144
|
for (const [fieldName, options] of arrangeColumns(model.attributes)) {
|
|
142
145
|
const fieldOptions = options as Attribute
|
|
143
|
-
const
|
|
144
|
-
|
|
146
|
+
const fieldNameFormatted = snakeCase(fieldName)
|
|
147
|
+
const columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule)
|
|
148
|
+
migrationContent += ` .addColumn('${fieldNameFormatted}', ${columnType}`
|
|
145
149
|
|
|
146
150
|
// Check if there are configurations that require the lambda function
|
|
147
|
-
if (fieldOptions.unique || fieldOptions
|
|
151
|
+
if (fieldOptions.unique || fieldOptions?.required) {
|
|
148
152
|
migrationContent += `, col => col`
|
|
149
153
|
if (fieldOptions.unique) migrationContent += `.unique()`
|
|
150
|
-
if (fieldOptions
|
|
154
|
+
if (fieldOptions?.required) migrationContent += `.notNull()`
|
|
151
155
|
migrationContent += ``
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
migrationContent += `)\n`
|
|
155
159
|
}
|
|
156
160
|
|
|
161
|
+
if (false !== twoFactorEnabled && twoFactorEnabled) {
|
|
162
|
+
migrationContent += ` .addColumn('two_factor_secret', 'varchar(255)')\n`
|
|
163
|
+
}
|
|
164
|
+
|
|
157
165
|
if (otherModelRelations?.length) {
|
|
158
166
|
for (const modelRelation of otherModelRelations) {
|
|
159
167
|
migrationContent += ` .addColumn('${modelRelation.foreignKey}', 'integer', (col) =>
|
|
160
|
-
col.references('${modelRelation.relationTable}.id').onDelete('cascade')
|
|
168
|
+
col.references('${modelRelation.relationTable}.id').onDelete('cascade')
|
|
161
169
|
) \n`
|
|
162
170
|
}
|
|
163
171
|
}
|
|
@@ -178,7 +186,6 @@ async function createTableMigration(modelPath: string): Promise<void> {
|
|
|
178
186
|
const migrationFileName = `${timestamp}-create-${tableName}-table.ts`
|
|
179
187
|
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
180
188
|
|
|
181
|
-
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
182
189
|
Bun.write(migrationFilePath, migrationContent)
|
|
183
190
|
|
|
184
191
|
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
@@ -208,7 +215,6 @@ async function createPivotTableMigration(model: Model, modelPath: string) {
|
|
|
208
215
|
const migrationFileName = `${timestamp}-create-${pivotTable.table}-table.ts`
|
|
209
216
|
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
210
217
|
|
|
211
|
-
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
212
218
|
Bun.write(migrationFilePath, migrationContent)
|
|
213
219
|
|
|
214
220
|
log.success(`Created pivot migration: ${migrationFileName}`)
|
|
@@ -222,12 +228,15 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
222
228
|
const modelName = getModelName(model, modelPath)
|
|
223
229
|
const tableName = await getTableName(model, modelPath)
|
|
224
230
|
let hasChanged = false
|
|
231
|
+
|
|
225
232
|
// Assuming you have a function to get the fields from the last migration
|
|
226
233
|
// For simplicity, this is not implemented here
|
|
227
234
|
const lastMigrationFields = await getLastMigrationFields(modelName)
|
|
228
235
|
const lastFields = lastMigrationFields ?? {}
|
|
229
236
|
const currentFields = model.attributes as Attributes
|
|
230
237
|
|
|
238
|
+
// Determine fields to add and remove
|
|
239
|
+
|
|
231
240
|
const changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields))
|
|
232
241
|
|
|
233
242
|
const fieldsToAdd = changes?.added || []
|
|
@@ -237,7 +246,6 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
237
246
|
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
238
247
|
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
239
248
|
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
240
|
-
migrationContent += ` await db.schema.alterTable('${tableName}')\n`
|
|
241
249
|
|
|
242
250
|
if (fieldsToAdd.length || fieldsToRemove.length) {
|
|
243
251
|
hasChanged = true
|
|
@@ -273,6 +281,7 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
273
281
|
|
|
274
282
|
migrationContent += `)\n\n`
|
|
275
283
|
}
|
|
284
|
+
|
|
276
285
|
// Remove fields that no longer exist
|
|
277
286
|
for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
|
|
278
287
|
|
|
@@ -286,8 +295,6 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
286
295
|
migrationContent += reArrangeColumns(model.attributes, tableName)
|
|
287
296
|
}
|
|
288
297
|
|
|
289
|
-
migrationContent += ` .execute();\n`
|
|
290
|
-
|
|
291
298
|
migrationContent += `}\n`
|
|
292
299
|
|
|
293
300
|
const timestamp = new Date().getTime().toString()
|
|
@@ -299,25 +306,25 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
299
306
|
|
|
300
307
|
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
301
308
|
}
|
|
309
|
+
}
|
|
302
310
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
let previousField = ''
|
|
308
|
-
for (const [fieldName, options] of fields) {
|
|
309
|
-
const fieldNameFormatted = snakeCase(fieldName)
|
|
311
|
+
function reArrangeColumns(attributes: Attributes | undefined, tableName: string): string {
|
|
312
|
+
const fields = arrangeColumns(attributes)
|
|
313
|
+
let migrationContent = ''
|
|
310
314
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
|
|
315
|
-
\`.execute(db)\n\n`
|
|
316
|
-
}
|
|
315
|
+
let previousField = ''
|
|
316
|
+
for (const [fieldName, options] of fields) {
|
|
317
|
+
const fieldNameFormatted = snakeCase(fieldName)
|
|
317
318
|
|
|
318
|
-
|
|
319
|
+
if (previousField) {
|
|
320
|
+
migrationContent += `await sql\`
|
|
321
|
+
ALTER TABLE ${tableName}
|
|
322
|
+
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
|
|
323
|
+
\`.execute(db)\n\n`
|
|
319
324
|
}
|
|
320
325
|
|
|
321
|
-
|
|
326
|
+
previousField = fieldNameFormatted
|
|
322
327
|
}
|
|
328
|
+
|
|
329
|
+
return migrationContent
|
|
323
330
|
}
|
package/src/migrations.ts
CHANGED
|
@@ -88,6 +88,7 @@ export async function generateMigrations() {
|
|
|
88
88
|
|
|
89
89
|
for (const file of modelFiles) {
|
|
90
90
|
log.debug('Generating migration for:', file)
|
|
91
|
+
|
|
91
92
|
await generateMigration(file)
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -99,11 +100,11 @@ export async function generateMigrations() {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
export async function generateMigration(modelPath: string) {
|
|
102
|
-
if (driver === 'sqlite') generateSqliteMigration(modelPath)
|
|
103
|
+
if (driver === 'sqlite') await generateSqliteMigration(modelPath)
|
|
103
104
|
|
|
104
|
-
if (driver === 'mysql') generateMysqlMigration(modelPath)
|
|
105
|
+
if (driver === 'mysql') await generateMysqlMigration(modelPath)
|
|
105
106
|
|
|
106
|
-
if (driver === 'postgres') generatePostgresMigration(modelPath)
|
|
107
|
+
if (driver === 'postgres') await generatePostgresMigration(modelPath)
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
export async function getExecutedMigrations() {
|
package/src/seeder.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { italic, log } from '@stacksjs/cli'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
3
|
import { modelTableName } from '@stacksjs/orm'
|
|
4
|
+
import { getModelName, getTableName } from '@stacksjs/orm'
|
|
4
5
|
import { path } from '@stacksjs/path'
|
|
6
|
+
import { makeHash } from '@stacksjs/security'
|
|
5
7
|
import { fs, glob } from '@stacksjs/storage'
|
|
6
|
-
import { singular, snakeCase } from '@stacksjs/strings'
|
|
8
|
+
import { plural, singular, snakeCase } from '@stacksjs/strings'
|
|
7
9
|
import type { Model, RelationConfig } from '@stacksjs/types'
|
|
8
10
|
import { isString } from '@stacksjs/validation'
|
|
11
|
+
|
|
9
12
|
import { generateMigrations, resetDatabase, runDatabaseMigration } from './migrations'
|
|
10
13
|
|
|
11
14
|
async function seedModel(name: string, model?: Model) {
|
|
@@ -25,8 +28,6 @@ async function seedModel(name: string, model?: Model) {
|
|
|
25
28
|
const records = []
|
|
26
29
|
const otherRelations = await fetchOtherModelRelations(model)
|
|
27
30
|
|
|
28
|
-
log.debug(otherRelations)
|
|
29
|
-
|
|
30
31
|
for (let i = 0; i < seedCount; i++) {
|
|
31
32
|
const record: any = {}
|
|
32
33
|
|
|
@@ -35,13 +36,19 @@ async function seedModel(name: string, model?: Model) {
|
|
|
35
36
|
const field = model.attributes[fieldName]
|
|
36
37
|
|
|
37
38
|
// Use the factory function if available, otherwise leave the field undefined
|
|
38
|
-
|
|
39
|
+
if (formattedFieldName === 'password')
|
|
40
|
+
record[formattedFieldName] = field?.factory ? await makeHash('Test@123', { algorithm: 'bcrypt' }) : undefined
|
|
41
|
+
else record[formattedFieldName] = field?.factory ? field.factory() : undefined
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
if (otherRelations?.length) {
|
|
42
45
|
for (let j = 0; j < otherRelations.length; j++) {
|
|
43
46
|
const relationElement = otherRelations[j] as RelationConfig
|
|
44
47
|
|
|
48
|
+
if (relationElement.relationship === 'belongsToMany') {
|
|
49
|
+
await seedPivotRelation(relationElement)
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
record[relationElement?.foreignKey] = await seedModelRelation(relationElement?.relationModel as string)
|
|
46
53
|
}
|
|
47
54
|
}
|
|
@@ -53,6 +60,51 @@ async function seedModel(name: string, model?: Model) {
|
|
|
53
60
|
await db.insertInto(tableName).values(records).execute()
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
async function seedPivotRelation(relation: RelationConfig): Promise<any> {
|
|
64
|
+
const record: any = {}
|
|
65
|
+
const record2: any = {}
|
|
66
|
+
const pivotRecord: any = {}
|
|
67
|
+
|
|
68
|
+
const modelInstance = (await import(path.userModelsPath(relation?.model))).default
|
|
69
|
+
const relationModelInstance = (await import(path.userModelsPath(relation?.relationModel))).default
|
|
70
|
+
|
|
71
|
+
if (!relationModelInstance) return 1
|
|
72
|
+
|
|
73
|
+
const relationModelTable = relationModelInstance.table
|
|
74
|
+
const relationTable = relation.table
|
|
75
|
+
const pivotTable = relation.pivotTable
|
|
76
|
+
const modelKey = `${singular(relationTable)}_id`
|
|
77
|
+
const foreignKey = relation.foreignKey
|
|
78
|
+
|
|
79
|
+
for (const fieldName in relationModelInstance.attributes) {
|
|
80
|
+
const formattedFieldName = snakeCase(fieldName)
|
|
81
|
+
const field = relationModelInstance.attributes[fieldName]
|
|
82
|
+
// Use the factory function if available, otherwise leave the field undefined
|
|
83
|
+
if (formattedFieldName === 'password')
|
|
84
|
+
record[formattedFieldName] = field?.factory ? await makeHash('Test@123', { algorithm: 'bcrypt' }) : undefined
|
|
85
|
+
else record[formattedFieldName] = field?.factory ? field.factory() : undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const fieldName in modelInstance.attributes) {
|
|
89
|
+
const formattedFieldName = snakeCase(fieldName)
|
|
90
|
+
const field = modelInstance.attributes[fieldName]
|
|
91
|
+
// Use the factory function if available, otherwise leave the field undefined
|
|
92
|
+
if (formattedFieldName === 'password')
|
|
93
|
+
record2[formattedFieldName] = field?.factory ? await makeHash('Test@123', { algorithm: 'bcrypt' }) : undefined
|
|
94
|
+
else record2[formattedFieldName] = field?.factory ? field.factory() : undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = await db.insertInto(relationModelTable).values(record).executeTakeFirstOrThrow()
|
|
98
|
+
const data2 = await db.insertInto(relationTable).values(record2).executeTakeFirstOrThrow()
|
|
99
|
+
const relationData = data.insertId || 1
|
|
100
|
+
const modelData = data2.insertId || 1
|
|
101
|
+
|
|
102
|
+
pivotRecord[foreignKey] = relationData
|
|
103
|
+
pivotRecord[modelKey] = modelData
|
|
104
|
+
|
|
105
|
+
if (pivotTable) await db.insertInto(pivotTable).values(pivotRecord).executeTakeFirstOrThrow()
|
|
106
|
+
}
|
|
107
|
+
|
|
56
108
|
async function seedModelRelation(modelName: string): Promise<BigInt | number> {
|
|
57
109
|
const modelInstance = (await import(path.userModelsPath(modelName))).default
|
|
58
110
|
|
|
@@ -65,7 +117,10 @@ async function seedModelRelation(modelName: string): Promise<BigInt | number> {
|
|
|
65
117
|
const formattedFieldName = snakeCase(fieldName)
|
|
66
118
|
const field = modelInstance.attributes[fieldName]
|
|
67
119
|
// Use the factory function if available, otherwise leave the field undefined
|
|
68
|
-
|
|
120
|
+
|
|
121
|
+
if (formattedFieldName === 'password')
|
|
122
|
+
record[formattedFieldName] = field?.factory ? await makeHash(field.factory(), { algorithm: 'bcrypt' }) : undefined
|
|
123
|
+
else record[formattedFieldName] = field?.factory ? field.factory() : undefined
|
|
69
124
|
}
|
|
70
125
|
|
|
71
126
|
const data = await db.insertInto(table).values(record).executeTakeFirstOrThrow()
|
|
@@ -73,10 +128,13 @@ async function seedModelRelation(modelName: string): Promise<BigInt | number> {
|
|
|
73
128
|
return data.insertId || 1
|
|
74
129
|
}
|
|
75
130
|
|
|
76
|
-
export async function getRelations(model: Model): Promise<RelationConfig[]> {
|
|
131
|
+
export async function getRelations(model: Model, modelPath: string): Promise<RelationConfig[]> {
|
|
77
132
|
const relationsArray = ['hasOne', 'hasMany', 'belongsToMany', 'hasOneThrough']
|
|
78
133
|
const relationships = []
|
|
79
134
|
|
|
135
|
+
const modelName = getModelName(model, modelPath)
|
|
136
|
+
const tableName = getTableName(model, modelPath)
|
|
137
|
+
|
|
80
138
|
for (const relation of relationsArray) {
|
|
81
139
|
if (hasRelations(model, relation)) {
|
|
82
140
|
for (const relationInstance of model[relation]) {
|
|
@@ -88,18 +146,18 @@ export async function getRelations(model: Model): Promise<RelationConfig[]> {
|
|
|
88
146
|
|
|
89
147
|
const modelRelationPath = path.userModelsPath(`${relationModel}.ts`)
|
|
90
148
|
const modelRelation = (await import(modelRelationPath)).default
|
|
91
|
-
const formattedModelName =
|
|
149
|
+
const formattedModelName = modelName?.toLowerCase()
|
|
92
150
|
|
|
93
151
|
relationships.push({
|
|
94
152
|
relationship: relation,
|
|
95
153
|
model: relationModel,
|
|
96
154
|
table: modelRelation.table,
|
|
97
|
-
relationModel:
|
|
98
|
-
relationTable:
|
|
155
|
+
relationModel: modelName,
|
|
156
|
+
relationTable: tableName,
|
|
99
157
|
foreignKey: relationInstance.foreignKey || `${formattedModelName}_id`,
|
|
100
|
-
relationName: relationInstance.relationName
|
|
101
|
-
throughModel: relationInstance.through
|
|
102
|
-
throughForeignKey: relationInstance.throughForeignKey
|
|
158
|
+
relationName: relationInstance.relationName,
|
|
159
|
+
throughModel: relationInstance.through,
|
|
160
|
+
throughForeignKey: relationInstance.throughForeignKey,
|
|
103
161
|
pivotTable: relationInstance?.pivotTable || getPivotTableName(formattedModelName || '', modelRelation.table),
|
|
104
162
|
})
|
|
105
163
|
}
|
|
@@ -117,6 +175,7 @@ function getPivotTableName(formattedModelName: string, modelRelationTable: strin
|
|
|
117
175
|
models.sort()
|
|
118
176
|
|
|
119
177
|
models[0] = singular(models[0] || '')
|
|
178
|
+
models[1] = plural(models[1] || '')
|
|
120
179
|
|
|
121
180
|
// Join the sorted array with an underscore
|
|
122
181
|
const pivotTableName = models.join('_')
|
|
@@ -134,7 +193,7 @@ export async function fetchOtherModelRelations(model: Model): Promise<RelationCo
|
|
|
134
193
|
|
|
135
194
|
if (model.name === modelFile.default.name) continue
|
|
136
195
|
|
|
137
|
-
const relations = await getRelations(modelFile.default)
|
|
196
|
+
const relations = await getRelations(modelFile.default, modelFileElement)
|
|
138
197
|
|
|
139
198
|
if (!relations.length) continue
|
|
140
199
|
|
|
@@ -152,21 +211,21 @@ function hasRelations(obj: any, key: string): boolean {
|
|
|
152
211
|
|
|
153
212
|
export async function seed() {
|
|
154
213
|
// TODO: need to check other databases too
|
|
155
|
-
const dbPath = path.userDatabasePath('stacks.sqlite')
|
|
214
|
+
// const dbPath = path.userDatabasePath('stacks.sqlite')
|
|
156
215
|
|
|
157
|
-
if (!fs.existsSync(dbPath)) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
216
|
+
// if (!fs.existsSync(dbPath)) {
|
|
217
|
+
// log.warn('No database found, configuring it...')
|
|
218
|
+
// // first, ensure the database is reset
|
|
219
|
+
// await resetDatabase()
|
|
161
220
|
|
|
162
|
-
|
|
163
|
-
|
|
221
|
+
// // then, generate the migrations
|
|
222
|
+
// await generateMigrations()
|
|
164
223
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} else {
|
|
168
|
-
|
|
169
|
-
}
|
|
224
|
+
// // finally, migrate the database
|
|
225
|
+
// await runDatabaseMigration()
|
|
226
|
+
// } else {
|
|
227
|
+
// log.debug('Database configured...')
|
|
228
|
+
// }
|
|
170
229
|
|
|
171
230
|
// if a custom seeder exists, use it instead
|
|
172
231
|
const customSeederPath = path.userDatabasePath('seeder.ts')
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { database } from '@stacksjs/config'
|
|
2
2
|
import { log } from '@stacksjs/logging'
|
|
3
3
|
import type { Database } from '@stacksjs/orm'
|
|
4
|
+
import { plural, snakeCase } from '@stacksjs/strings'
|
|
5
|
+
import type { Model } from '@stacksjs/types'
|
|
4
6
|
import { Kysely, MysqlDialect, PostgresDialect, sql } from 'kysely'
|
|
5
7
|
import { BunWorkerDialect } from 'kysely-bun-worker'
|
|
6
8
|
import { createPool } from 'mysql2'
|