@stacksjs/database 0.64.6 → 0.65.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 +55 -55
- package/dist/index.js.map +136 -142
- package/package.json +16 -22
- package/src/drivers/index.ts +65 -171
- package/src/drivers/mysql.ts +95 -54
- package/src/drivers/postgres.ts +34 -32
- package/src/drivers/sqlite.ts +165 -62
- package/src/index.ts +2 -1
- package/src/migrations.ts +38 -61
- package/src/seeder.ts +17 -97
- package/src/table.ts +1 -1
- package/src/types.ts +2 -0
- package/src/utils.ts +19 -11
package/src/drivers/sqlite.ts
CHANGED
|
@@ -1,82 +1,108 @@
|
|
|
1
|
+
import type { Attribute, Attributes, Model } from '@stacksjs/types'
|
|
1
2
|
import { italic, log } from '@stacksjs/cli'
|
|
3
|
+
import { app } from '@stacksjs/config'
|
|
2
4
|
import { db } from '@stacksjs/database'
|
|
3
|
-
import { ok } from '@stacksjs/error-handling'
|
|
4
|
-
import { getModelName, getTableName } from '@stacksjs/orm'
|
|
5
|
+
import { type Ok, ok } from '@stacksjs/error-handling'
|
|
6
|
+
import { fetchOtherModelRelations, getModelName, getPivotTables, getTableName } from '@stacksjs/orm'
|
|
5
7
|
import { path } from '@stacksjs/path'
|
|
6
|
-
import { fs,
|
|
8
|
+
import { fs, globSync } from '@stacksjs/storage'
|
|
7
9
|
import { snakeCase } from '@stacksjs/strings'
|
|
8
|
-
import type { Attribute, Attributes, Model } from '@stacksjs/types'
|
|
9
10
|
import {
|
|
10
11
|
arrangeColumns,
|
|
11
12
|
checkPivotMigration,
|
|
12
|
-
|
|
13
|
+
fetchTables,
|
|
13
14
|
findDifferingKeys,
|
|
14
15
|
getLastMigrationFields,
|
|
15
|
-
getPivotTables,
|
|
16
16
|
hasTableBeenMigrated,
|
|
17
17
|
isArrayEqual,
|
|
18
18
|
mapFieldTypeToColumnType,
|
|
19
19
|
pluckChanges,
|
|
20
20
|
} from '.'
|
|
21
21
|
|
|
22
|
-
export async function resetSqliteDatabase() {
|
|
23
|
-
|
|
22
|
+
export async function resetSqliteDatabase(): Promise<Ok<string, never>> {
|
|
23
|
+
await deleteFrameworkModels()
|
|
24
|
+
await deleteMigrationFiles()
|
|
25
|
+
await dropSqliteTables()
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
return ok('All tables dropped successfully!')
|
|
28
|
+
}
|
|
26
29
|
|
|
30
|
+
export async function deleteMigrationFiles(): Promise<void> {
|
|
27
31
|
const files = await fs.readdir(path.userMigrationsPath())
|
|
28
|
-
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
29
|
-
|
|
30
|
-
const userModelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (files.length) {
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
if (file.endsWith('.ts')) {
|
|
36
|
+
const migrationPath = path.userMigrationsPath(`${file}`)
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
if (fs.existsSync(migrationPath))
|
|
39
|
+
await Bun.$`rm ${migrationPath}`
|
|
40
|
+
}
|
|
41
|
+
}
|
|
38
42
|
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function deleteFrameworkModels(): Promise<void> {
|
|
46
|
+
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
39
47
|
|
|
40
48
|
if (modelFiles.length) {
|
|
41
49
|
for (const modelFile of modelFiles) {
|
|
42
50
|
if (modelFile.endsWith('.ts')) {
|
|
43
51
|
const modelPath = path.frameworkPath(`database/models/${modelFile}`)
|
|
44
52
|
|
|
45
|
-
if (fs.existsSync(modelPath))
|
|
53
|
+
if (fs.existsSync(modelPath))
|
|
54
|
+
await Bun.$`rm ${modelPath}`
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
}
|
|
58
|
+
}
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const migrationPath = path.userMigrationsPath(`${file}`)
|
|
60
|
+
export async function dropSqliteTables(): Promise<void> {
|
|
61
|
+
const userModelFiles = globSync([path.userModelsPath('*.ts')], { absolute: true })
|
|
62
|
+
const tables = await fetchTables()
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
for (const table of tables) await db.schema.dropTable(table).ifExists().execute()
|
|
65
|
+
await db.schema.dropTable('migrations').ifExists().execute()
|
|
66
|
+
await db.schema.dropTable('migration_locks').ifExists().execute()
|
|
67
|
+
await db.schema.dropTable('passkeys').ifExists().execute()
|
|
68
|
+
|
|
69
|
+
for (const userModel of userModelFiles) {
|
|
70
|
+
const userModelPath = (await import(userModel)).default
|
|
71
|
+
const pivotTables = await getPivotTables(userModelPath, userModel)
|
|
72
|
+
|
|
73
|
+
for (const pivotTable of pivotTables) await db.schema.dropTable(pivotTable.table).ifExists().execute()
|
|
58
74
|
}
|
|
75
|
+
}
|
|
59
76
|
|
|
60
|
-
|
|
77
|
+
export function fetchSqliteFile(): string {
|
|
78
|
+
if (app.env === 'testing') {
|
|
79
|
+
return fetchTestSqliteFile()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return path.userDatabasePath('stacks.sqlite')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function fetchTestSqliteFile(): string {
|
|
86
|
+
return path.userDatabasePath('stacks_testing.sqlite')
|
|
61
87
|
}
|
|
62
88
|
|
|
63
|
-
export async function generateSqliteMigration(modelPath: string) {
|
|
89
|
+
export async function generateSqliteMigration(modelPath: string): Promise<void> {
|
|
64
90
|
// check if any files are in the database folder
|
|
65
|
-
const files = await fs.readdir(path.userMigrationsPath())
|
|
91
|
+
// const files = await fs.readdir(path.userMigrationsPath())
|
|
66
92
|
|
|
67
|
-
if (files.length === 0) {
|
|
68
|
-
|
|
93
|
+
// if (files.length === 0) {
|
|
94
|
+
// log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
// // delete the *.ts files in the database/models folder
|
|
97
|
+
// const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
72
98
|
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
// if (modelFiles.length) {
|
|
100
|
+
// log.debug('No existing model files in framework path...')
|
|
75
101
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
102
|
+
// for (const file of modelFiles)
|
|
103
|
+
// if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
|
|
104
|
+
// }
|
|
105
|
+
// }
|
|
80
106
|
|
|
81
107
|
const model = (await import(modelPath)).default as Model
|
|
82
108
|
const fileName = path.basename(modelPath)
|
|
@@ -101,7 +127,8 @@ export async function generateSqliteMigration(modelPath: string) {
|
|
|
101
127
|
|
|
102
128
|
haveFieldsChanged = true
|
|
103
129
|
log.debug(`Fields have changed for ${tableName}`)
|
|
104
|
-
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
105
132
|
log.debug(`Fields have not been generated for ${tableName}`)
|
|
106
133
|
}
|
|
107
134
|
|
|
@@ -116,17 +143,43 @@ export async function generateSqliteMigration(modelPath: string) {
|
|
|
116
143
|
|
|
117
144
|
log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`)
|
|
118
145
|
|
|
119
|
-
if (haveFieldsChanged)
|
|
146
|
+
if (haveFieldsChanged)
|
|
147
|
+
await createAlterTableMigration(modelPath)
|
|
120
148
|
else await createTableMigration(modelPath)
|
|
121
149
|
}
|
|
122
150
|
|
|
151
|
+
export async function copyModelFiles(modelPath: string): Promise<void> {
|
|
152
|
+
const model = (await import(modelPath)).default as Model
|
|
153
|
+
const fileName = path.basename(modelPath)
|
|
154
|
+
const tableName = await getTableName(model, modelPath)
|
|
155
|
+
|
|
156
|
+
const fieldsString = JSON.stringify(model.attributes, null, 2) // Pretty print the JSON
|
|
157
|
+
const copiedModelPath = path.frameworkPath(`database/models/${fileName}`)
|
|
158
|
+
|
|
159
|
+
// if the file exists, we need to check if the fields have changed
|
|
160
|
+
if (fs.existsSync(copiedModelPath)) {
|
|
161
|
+
log.debug(`Fields have already been generated for ${tableName}`)
|
|
162
|
+
|
|
163
|
+
const previousFields = await getLastMigrationFields(fileName)
|
|
164
|
+
const previousFieldsString = JSON.stringify(previousFields, null, 2) // Convert to string for comparison
|
|
165
|
+
|
|
166
|
+
if (previousFieldsString === fieldsString) {
|
|
167
|
+
log.debug(`Fields have not changed for ${tableName}`)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// store the fields of the model to a file
|
|
173
|
+
await Bun.$`cp ${modelPath} ${copiedModelPath}`
|
|
174
|
+
}
|
|
123
175
|
async function createTableMigration(modelPath: string) {
|
|
124
176
|
log.debug('createTableMigration modelPath:', modelPath)
|
|
125
177
|
|
|
126
178
|
const model = (await import(modelPath)).default as Model
|
|
127
|
-
const tableName =
|
|
179
|
+
const tableName = getTableName(model, modelPath)
|
|
128
180
|
|
|
129
|
-
const twoFactorEnabled
|
|
181
|
+
const twoFactorEnabled
|
|
182
|
+
= model.traits?.useAuth && typeof model.traits.useAuth !== 'boolean' ? model.traits.useAuth.useTwoFactor : false
|
|
130
183
|
|
|
131
184
|
await createPivotTableMigration(model, modelPath)
|
|
132
185
|
const otherModelRelations = await fetchOtherModelRelations(model, modelPath)
|
|
@@ -134,6 +187,11 @@ async function createTableMigration(modelPath: string) {
|
|
|
134
187
|
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
|
|
135
188
|
const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
|
|
136
189
|
|
|
190
|
+
const usePasskey = (typeof model.traits?.useAuth === 'object' && model.traits.useAuth.usePasskey) ?? false
|
|
191
|
+
|
|
192
|
+
if (usePasskey)
|
|
193
|
+
await createPasskeyMigration()
|
|
194
|
+
|
|
137
195
|
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
138
196
|
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
139
197
|
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
@@ -144,21 +202,23 @@ async function createTableMigration(modelPath: string) {
|
|
|
144
202
|
for (const [fieldName, options] of arrangeColumns(model.attributes)) {
|
|
145
203
|
const fieldOptions = options as Attribute
|
|
146
204
|
const fieldNameFormatted = snakeCase(fieldName)
|
|
147
|
-
const columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule)
|
|
205
|
+
const columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule, 'sqlite')
|
|
148
206
|
migrationContent += ` .addColumn('${fieldNameFormatted}', ${columnType}`
|
|
149
207
|
|
|
150
208
|
// Check if there are configurations that require the lambda function
|
|
151
209
|
if (fieldOptions.unique || fieldOptions?.required) {
|
|
152
210
|
migrationContent += `, col => col`
|
|
153
|
-
if (fieldOptions.unique)
|
|
154
|
-
|
|
211
|
+
if (fieldOptions.unique)
|
|
212
|
+
migrationContent += `.unique()`
|
|
213
|
+
if (fieldOptions?.required)
|
|
214
|
+
migrationContent += `.notNull()`
|
|
155
215
|
migrationContent += ``
|
|
156
216
|
}
|
|
157
217
|
|
|
158
218
|
migrationContent += `)\n`
|
|
159
219
|
}
|
|
160
220
|
|
|
161
|
-
if (
|
|
221
|
+
if (twoFactorEnabled !== false && twoFactorEnabled) {
|
|
162
222
|
migrationContent += ` .addColumn('two_factor_secret', 'varchar(255)')\n`
|
|
163
223
|
}
|
|
164
224
|
|
|
@@ -170,14 +230,18 @@ async function createTableMigration(modelPath: string) {
|
|
|
170
230
|
}
|
|
171
231
|
}
|
|
172
232
|
|
|
233
|
+
if (usePasskey)
|
|
234
|
+
migrationContent += ` .addColumn('public_passkey', 'text')\n`
|
|
235
|
+
|
|
173
236
|
// Append created_at and updated_at columns if useTimestamps is true
|
|
174
237
|
if (useTimestamps) {
|
|
175
|
-
migrationContent
|
|
176
|
-
|
|
238
|
+
migrationContent
|
|
239
|
+
+= ' .addColumn(\'created_at\', \'timestamp\', col => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`))\n'
|
|
240
|
+
migrationContent += ' .addColumn(\'updated_at\', \'timestamp\')\n'
|
|
177
241
|
}
|
|
178
242
|
|
|
179
|
-
|
|
180
|
-
|
|
243
|
+
if (useSoftDeletes)
|
|
244
|
+
migrationContent += ` .addColumn('deleted_at', 'text')\n`
|
|
181
245
|
|
|
182
246
|
migrationContent += ` .execute()\n`
|
|
183
247
|
migrationContent += `}\n`
|
|
@@ -194,12 +258,14 @@ async function createTableMigration(modelPath: string) {
|
|
|
194
258
|
async function createPivotTableMigration(model: Model, modelPath: string) {
|
|
195
259
|
const pivotTables = await getPivotTables(model, modelPath)
|
|
196
260
|
|
|
197
|
-
if (!pivotTables.length)
|
|
261
|
+
if (!pivotTables.length)
|
|
262
|
+
return
|
|
198
263
|
|
|
199
264
|
for (const pivotTable of pivotTables) {
|
|
200
265
|
const hasBeenMigrated = await checkPivotMigration(pivotTable.table)
|
|
201
266
|
|
|
202
|
-
if (hasBeenMigrated)
|
|
267
|
+
if (hasBeenMigrated)
|
|
268
|
+
return
|
|
203
269
|
|
|
204
270
|
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
205
271
|
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
@@ -213,6 +279,7 @@ async function createPivotTableMigration(model: Model, modelPath: string) {
|
|
|
213
279
|
|
|
214
280
|
const timestamp = new Date().getTime().toString()
|
|
215
281
|
const migrationFileName = `${timestamp}-create-${pivotTable.table}-table.ts`
|
|
282
|
+
|
|
216
283
|
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
217
284
|
|
|
218
285
|
Bun.write(migrationFilePath, migrationContent)
|
|
@@ -221,12 +288,45 @@ async function createPivotTableMigration(model: Model, modelPath: string) {
|
|
|
221
288
|
}
|
|
222
289
|
}
|
|
223
290
|
|
|
224
|
-
|
|
225
|
-
|
|
291
|
+
async function createPasskeyMigration() {
|
|
292
|
+
const hasBeenMigrated = await hasTableBeenMigrated('users')
|
|
293
|
+
|
|
294
|
+
if (hasBeenMigrated)
|
|
295
|
+
return
|
|
226
296
|
|
|
297
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n import { sql } from '@stacksjs/database'\n\n`
|
|
298
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
299
|
+
migrationContent += ` await db.schema\n`
|
|
300
|
+
migrationContent += ` .createTable('passkeys')\n`
|
|
301
|
+
migrationContent += ` .addColumn('id', 'text')\n`
|
|
302
|
+
migrationContent += ` .addColumn('cred_public_key', 'text')\n`
|
|
303
|
+
migrationContent += ` .addColumn('user_id', 'integer')\n`
|
|
304
|
+
migrationContent += ` .addColumn('webauthn_user_id', 'varchar(255)')\n`
|
|
305
|
+
migrationContent += ` .addColumn('counter', 'integer')\n`
|
|
306
|
+
migrationContent += ` .addColumn('device_type', 'varchar(255)')\n`
|
|
307
|
+
migrationContent += ` .addColumn('credential_type', 'varchar(255)')\n`
|
|
308
|
+
migrationContent += ` .addColumn('backup_eligible', 'boolean')\n`
|
|
309
|
+
migrationContent += ` .addColumn('backup_status', 'boolean')\n`
|
|
310
|
+
migrationContent += ` .addColumn('transports', 'varchar(255)')\n`
|
|
311
|
+
migrationContent += ` .addColumn('last_used_at', 'text')\n`
|
|
312
|
+
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
|
|
313
|
+
migrationContent += ` .execute()\n`
|
|
314
|
+
migrationContent += ` }\n`
|
|
315
|
+
|
|
316
|
+
const timestamp = new Date().getTime().toString()
|
|
317
|
+
const migrationFileName = `${timestamp}-create-passkeys-table.ts`
|
|
318
|
+
|
|
319
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
320
|
+
|
|
321
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
322
|
+
|
|
323
|
+
log.success(`Created pivot migration: ${migrationFileName}`)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function createAlterTableMigration(modelPath: string) {
|
|
227
327
|
const model = (await import(modelPath)).default as Model
|
|
228
328
|
const modelName = getModelName(model, modelPath)
|
|
229
|
-
const tableName =
|
|
329
|
+
const tableName = getTableName(model, modelPath)
|
|
230
330
|
let hasChanged = false
|
|
231
331
|
|
|
232
332
|
// Assuming you have a function to get the fields from the last migration
|
|
@@ -266,7 +366,7 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
266
366
|
// Add new fields
|
|
267
367
|
for (const fieldName of fieldsToAdd) {
|
|
268
368
|
const options = currentFields[fieldName] as Attribute
|
|
269
|
-
const columnType = mapFieldTypeToColumnType(options.validation?.rule)
|
|
369
|
+
const columnType = mapFieldTypeToColumnType(options.validation?.rule, 'sqlite')
|
|
270
370
|
const formattedFieldName = snakeCase(fieldName)
|
|
271
371
|
|
|
272
372
|
migrationContent += ` .addColumn('${formattedFieldName}', ${columnType}`
|
|
@@ -274,8 +374,10 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
274
374
|
// Check if there are configurations that require the lambda function
|
|
275
375
|
if (options.unique || options?.required) {
|
|
276
376
|
migrationContent += `, col => col`
|
|
277
|
-
if (options.unique)
|
|
278
|
-
|
|
377
|
+
if (options.unique)
|
|
378
|
+
migrationContent += `.unique()`
|
|
379
|
+
if (options?.required)
|
|
380
|
+
migrationContent += `.notNull()`
|
|
279
381
|
migrationContent += ``
|
|
280
382
|
}
|
|
281
383
|
|
|
@@ -285,10 +387,11 @@ export async function createAlterTableMigration(modelPath: string) {
|
|
|
285
387
|
// Remove fields that no longer exist
|
|
286
388
|
for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
|
|
287
389
|
|
|
288
|
-
if (fieldsToAdd.length || fieldsToRemove.length)
|
|
390
|
+
if (fieldsToAdd.length || fieldsToRemove.length)
|
|
391
|
+
migrationContent += ` .execute();\n`
|
|
289
392
|
|
|
290
|
-
const lastFieldOrder = Object.values(lastFields).map(
|
|
291
|
-
const currentFieldOrder = Object.values(currentFields).map(
|
|
393
|
+
const lastFieldOrder = Object.values(lastFields).map(attr => attr.order)
|
|
394
|
+
const currentFieldOrder = Object.values(currentFields).map(attr => attr.order)
|
|
292
395
|
|
|
293
396
|
if (!isArrayEqual(lastFieldOrder, currentFieldOrder)) {
|
|
294
397
|
hasChanged = true
|
|
@@ -313,7 +416,7 @@ function reArrangeColumns(attributes: Attributes | undefined, tableName: string)
|
|
|
313
416
|
let migrationContent = ''
|
|
314
417
|
|
|
315
418
|
let previousField = ''
|
|
316
|
-
for (const [fieldName
|
|
419
|
+
for (const [fieldName] of fields) {
|
|
317
420
|
const fieldNameFormatted = snakeCase(fieldName)
|
|
318
421
|
|
|
319
422
|
if (previousField) {
|
package/src/index.ts
CHANGED
package/src/migrations.ts
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { log } from '@stacksjs/cli'
|
|
2
2
|
import { database } from '@stacksjs/config'
|
|
3
|
-
import { err, ok } from '@stacksjs/error-handling'
|
|
4
|
-
import { extractFieldsFromModel } from '@stacksjs/orm'
|
|
3
|
+
import { type Err, err, handleError, type Ok, ok, type Result } from '@stacksjs/error-handling'
|
|
5
4
|
import { path } from '@stacksjs/path'
|
|
6
|
-
import { fs,
|
|
7
|
-
import type { Attribute, Attributes } from '@stacksjs/types'
|
|
5
|
+
import { fs, globSync } from '@stacksjs/storage'
|
|
8
6
|
import { $ } from 'bun'
|
|
9
|
-
import { FileMigrationProvider, Migrator } from 'kysely'
|
|
10
|
-
import { generateMysqlMigration, resetMysqlDatabase } from './drivers'
|
|
11
|
-
import { generatePostgresMigration, resetPostgresDatabase } from './drivers'
|
|
12
|
-
import { generateSqliteMigration, resetSqliteDatabase } from './drivers'
|
|
7
|
+
import { FileMigrationProvider, type MigrationResult, Migrator } from 'kysely'
|
|
8
|
+
import { generateMysqlMigration, generatePostgresMigration, generateSqliteMigration, resetMysqlDatabase, resetPostgresDatabase, resetSqliteDatabase } from './drivers'
|
|
13
9
|
import { db } from './utils'
|
|
14
10
|
|
|
15
11
|
const driver = database.default || ''
|
|
16
12
|
|
|
17
|
-
export const migrator = new Migrator({
|
|
13
|
+
export const migrator: Migrator = new Migrator({
|
|
18
14
|
db,
|
|
19
15
|
|
|
20
16
|
provider: new FileMigrationProvider({
|
|
@@ -39,15 +35,14 @@ export const migrator = new Migrator({
|
|
|
39
35
|
// }),
|
|
40
36
|
// })
|
|
41
37
|
|
|
42
|
-
export async function runDatabaseMigration() {
|
|
38
|
+
export async function runDatabaseMigration(): Promise<Result<MigrationResult[] | string, Error>> {
|
|
43
39
|
try {
|
|
44
40
|
log.info('Migrating database...')
|
|
45
41
|
|
|
46
42
|
const { error, results } = await migrator.migrateToLatest()
|
|
47
43
|
|
|
48
44
|
if (error) {
|
|
49
|
-
|
|
50
|
-
return err(error)
|
|
45
|
+
return err(handleError(error))
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
if (results?.length === 0) {
|
|
@@ -55,13 +50,14 @@ export async function runDatabaseMigration() {
|
|
|
55
50
|
return ok('No new migrations were executed')
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
if (results)
|
|
53
|
+
if (results)
|
|
54
|
+
return ok(results)
|
|
59
55
|
|
|
60
56
|
log.success('Database migration completed with no new migrations.')
|
|
61
57
|
return ok('Database migration completed with no new migrations.')
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return err(error)
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return err(handleError('Migration failed', error))
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
|
|
@@ -70,21 +66,22 @@ export interface MigrationOptions {
|
|
|
70
66
|
up: string
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
export async function resetDatabase() {
|
|
74
|
-
if (driver === 'sqlite')
|
|
75
|
-
|
|
76
|
-
if (driver === 'mysql')
|
|
77
|
-
|
|
78
|
-
if (driver === 'postgres')
|
|
69
|
+
export async function resetDatabase(): Promise<Ok<string, never>> {
|
|
70
|
+
if (driver === 'sqlite')
|
|
71
|
+
return await resetSqliteDatabase()
|
|
72
|
+
if (driver === 'mysql')
|
|
73
|
+
return await resetMysqlDatabase()
|
|
74
|
+
if (driver === 'postgres')
|
|
75
|
+
return await resetPostgresDatabase()
|
|
79
76
|
|
|
80
77
|
throw new Error('Unsupported database driver in resetDatabase')
|
|
81
78
|
}
|
|
82
79
|
|
|
83
|
-
export async function generateMigrations() {
|
|
80
|
+
export async function generateMigrations(): Promise<Ok<string, never> | Err<string, any>> {
|
|
84
81
|
try {
|
|
85
82
|
log.info('Generating migrations...')
|
|
86
83
|
|
|
87
|
-
const modelFiles =
|
|
84
|
+
const modelFiles = globSync([path.userModelsPath('*.ts')], { absolute: true })
|
|
88
85
|
|
|
89
86
|
for (const file of modelFiles) {
|
|
90
87
|
log.debug('Generating migration for:', file)
|
|
@@ -94,29 +91,24 @@ export async function generateMigrations() {
|
|
|
94
91
|
|
|
95
92
|
log.success('Migrations generated')
|
|
96
93
|
return ok('Migrations generated')
|
|
97
|
-
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
98
96
|
return err(error)
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
export async function generateMigration(modelPath: string) {
|
|
103
|
-
if (driver === 'sqlite')
|
|
104
|
-
|
|
105
|
-
if (driver === 'mysql') await generateMysqlMigration(modelPath)
|
|
100
|
+
export async function generateMigration(modelPath: string): Promise<void> {
|
|
101
|
+
if (driver === 'sqlite')
|
|
102
|
+
await generateSqliteMigration(modelPath)
|
|
106
103
|
|
|
107
|
-
if (driver === '
|
|
108
|
-
|
|
104
|
+
if (driver === 'mysql')
|
|
105
|
+
await generateMysqlMigration(modelPath)
|
|
109
106
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// @ts-expect-error the migrations table is not typed yet
|
|
113
|
-
return await db.selectFrom('migrations').select('name').execute()
|
|
114
|
-
} catch (error) {
|
|
115
|
-
return []
|
|
116
|
-
}
|
|
107
|
+
if (driver === 'postgres')
|
|
108
|
+
await generatePostgresMigration(modelPath)
|
|
117
109
|
}
|
|
118
110
|
|
|
119
|
-
export async function haveModelFieldsChangedSinceLastMigration(modelPath: string) {
|
|
111
|
+
export async function haveModelFieldsChangedSinceLastMigration(modelPath: string): Promise<boolean> {
|
|
120
112
|
log.debug(`haveModelFieldsChangedSinceLastMigration for model: ${modelPath}`)
|
|
121
113
|
|
|
122
114
|
// const model = await import(modelPath)
|
|
@@ -134,11 +126,11 @@ export async function haveModelFieldsChangedSinceLastMigration(modelPath: string
|
|
|
134
126
|
return !!gitHistory
|
|
135
127
|
}
|
|
136
128
|
|
|
137
|
-
export async function lastMigration() {
|
|
129
|
+
export async function lastMigration(): Promise<any> {
|
|
138
130
|
try {
|
|
139
|
-
// @ts-expect-error the migrations table is not typed yet
|
|
140
131
|
return await db.selectFrom('migrations').selectAll().orderBy('timestamp', 'desc').limit(1).execute()
|
|
141
|
-
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
142
134
|
console.error('Failed to get last migration:', error)
|
|
143
135
|
return { error }
|
|
144
136
|
}
|
|
@@ -146,28 +138,13 @@ export async function lastMigration() {
|
|
|
146
138
|
|
|
147
139
|
export async function lastMigrationDate(): Promise<string | undefined> {
|
|
148
140
|
try {
|
|
149
|
-
// @ts-expect-error the migrations table is not typed yet
|
|
150
141
|
return (await db.selectFrom('migrations').select('timestamp').orderBy('timestamp', 'desc').limit(1).execute())[0]
|
|
151
142
|
.timestamp
|
|
152
|
-
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
153
145
|
console.error('Failed to get last migration date:', error)
|
|
154
146
|
return undefined
|
|
155
147
|
}
|
|
156
148
|
}
|
|
157
149
|
|
|
158
|
-
|
|
159
|
-
// read the last migration file and extract the fields that were modified.
|
|
160
|
-
export async function getLastMigrationFields(modelName: string): Promise<Attribute> {
|
|
161
|
-
const oldModelPath = path.frameworkPath(`database/models/${modelName}`)
|
|
162
|
-
const model = (await import(oldModelPath)).default as Model
|
|
163
|
-
let fields = {} as Attributes
|
|
164
|
-
|
|
165
|
-
if (typeof model.attributes === 'object') fields = model.attributes
|
|
166
|
-
else fields = JSON.parse(model.attributes) as Attributes
|
|
167
|
-
|
|
168
|
-
return fields
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export async function getCurrentMigrationFields(modelPath: string): Promise<Attribute | undefined> {
|
|
172
|
-
return extractFieldsFromModel(modelPath)
|
|
173
|
-
}
|
|
150
|
+
export type { MigrationResult }
|