@stacksjs/database 0.66.0 → 0.67.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 +23 -26
- package/package.json +11 -13
- package/dist/index.js.map +0 -605
- package/src/column.ts +0 -36
- package/src/drivers/index.ts +0 -258
- package/src/drivers/mysql.ts +0 -389
- package/src/drivers/postgres.ts +0 -284
- package/src/drivers/sqlite.ts +0 -433
- package/src/index.ts +0 -6
- package/src/migrations.ts +0 -150
- package/src/schema.ts +0 -14
- package/src/seeder.ts +0 -166
- package/src/table.ts +0 -32
- package/src/types.ts +0 -5
- package/src/utils.ts +0 -62
package/src/drivers/sqlite.ts
DELETED
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
import type { Attribute, Attributes, Model } from '@stacksjs/types'
|
|
2
|
-
import { italic, log } from '@stacksjs/cli'
|
|
3
|
-
import { app } from '@stacksjs/config'
|
|
4
|
-
import { db } from '@stacksjs/database'
|
|
5
|
-
import { type Ok, ok } from '@stacksjs/error-handling'
|
|
6
|
-
import { fetchOtherModelRelations, getModelName, getPivotTables, getTableName } from '@stacksjs/orm'
|
|
7
|
-
import { path } from '@stacksjs/path'
|
|
8
|
-
import { fs, globSync } from '@stacksjs/storage'
|
|
9
|
-
import { snakeCase } from '@stacksjs/strings'
|
|
10
|
-
import {
|
|
11
|
-
arrangeColumns,
|
|
12
|
-
checkPivotMigration,
|
|
13
|
-
fetchTables,
|
|
14
|
-
findDifferingKeys,
|
|
15
|
-
getLastMigrationFields,
|
|
16
|
-
hasTableBeenMigrated,
|
|
17
|
-
isArrayEqual,
|
|
18
|
-
mapFieldTypeToColumnType,
|
|
19
|
-
pluckChanges,
|
|
20
|
-
} from '.'
|
|
21
|
-
|
|
22
|
-
export async function resetSqliteDatabase(): Promise<Ok<string, never>> {
|
|
23
|
-
await deleteFrameworkModels()
|
|
24
|
-
await deleteMigrationFiles()
|
|
25
|
-
await dropSqliteTables()
|
|
26
|
-
|
|
27
|
-
return ok('All tables dropped successfully!')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function deleteMigrationFiles(): Promise<void> {
|
|
31
|
-
const files = await fs.readdir(path.userMigrationsPath())
|
|
32
|
-
|
|
33
|
-
if (files.length) {
|
|
34
|
-
for (const file of files) {
|
|
35
|
-
if (file.endsWith('.ts')) {
|
|
36
|
-
const migrationPath = path.userMigrationsPath(`${file}`)
|
|
37
|
-
|
|
38
|
-
if (fs.existsSync(migrationPath))
|
|
39
|
-
await Bun.$`rm ${migrationPath}`
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function deleteFrameworkModels(): Promise<void> {
|
|
46
|
-
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
47
|
-
|
|
48
|
-
if (modelFiles.length) {
|
|
49
|
-
for (const modelFile of modelFiles) {
|
|
50
|
-
if (modelFile.endsWith('.ts')) {
|
|
51
|
-
const modelPath = path.frameworkPath(`database/models/${modelFile}`)
|
|
52
|
-
|
|
53
|
-
if (fs.existsSync(modelPath))
|
|
54
|
-
await Bun.$`rm ${modelPath}`
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function dropSqliteTables(): Promise<void> {
|
|
61
|
-
const userModelFiles = globSync([path.userModelsPath('*.ts')], { absolute: true })
|
|
62
|
-
const tables = await fetchTables()
|
|
63
|
-
|
|
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()
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
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')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function generateSqliteMigration(modelPath: string): Promise<void> {
|
|
90
|
-
// check if any files are in the database folder
|
|
91
|
-
// const files = await fs.readdir(path.userMigrationsPath())
|
|
92
|
-
|
|
93
|
-
// if (files.length === 0) {
|
|
94
|
-
// log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
|
|
95
|
-
|
|
96
|
-
// // delete the *.ts files in the database/models folder
|
|
97
|
-
// const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
98
|
-
|
|
99
|
-
// if (modelFiles.length) {
|
|
100
|
-
// log.debug('No existing model files in framework path...')
|
|
101
|
-
|
|
102
|
-
// for (const file of modelFiles)
|
|
103
|
-
// if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
|
|
104
|
-
// }
|
|
105
|
-
// }
|
|
106
|
-
|
|
107
|
-
const model = (await import(modelPath)).default as Model
|
|
108
|
-
const fileName = path.basename(modelPath)
|
|
109
|
-
const tableName = await getTableName(model, modelPath)
|
|
110
|
-
|
|
111
|
-
const fieldsString = JSON.stringify(model.attributes, null, 2) // Pretty print the JSON
|
|
112
|
-
const copiedModelPath = path.frameworkPath(`database/models/${fileName}`)
|
|
113
|
-
|
|
114
|
-
let haveFieldsChanged = false
|
|
115
|
-
|
|
116
|
-
// if the file exists, we need to check if the fields have changed
|
|
117
|
-
if (fs.existsSync(copiedModelPath)) {
|
|
118
|
-
log.debug(`Fields have already been generated for ${tableName}`)
|
|
119
|
-
|
|
120
|
-
const previousFields = await getLastMigrationFields(fileName)
|
|
121
|
-
const previousFieldsString = JSON.stringify(previousFields, null, 2) // Convert to string for comparison
|
|
122
|
-
|
|
123
|
-
if (previousFieldsString === fieldsString) {
|
|
124
|
-
log.debug(`Fields have not changed for ${tableName}`)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
haveFieldsChanged = true
|
|
129
|
-
log.debug(`Fields have changed for ${tableName}`)
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
log.debug(`Fields have not been generated for ${tableName}`)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// store the fields of the model to a file
|
|
136
|
-
await Bun.$`cp ${modelPath} ${copiedModelPath}`
|
|
137
|
-
|
|
138
|
-
// if the fields have changed, we need to create a new update migration
|
|
139
|
-
// if the fields have not changed, we need to migrate the table
|
|
140
|
-
|
|
141
|
-
// we need to check if this tableName has already been migrated
|
|
142
|
-
const hasBeenMigrated = await hasTableBeenMigrated(tableName)
|
|
143
|
-
|
|
144
|
-
log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`)
|
|
145
|
-
|
|
146
|
-
if (haveFieldsChanged)
|
|
147
|
-
await createAlterTableMigration(modelPath)
|
|
148
|
-
else await createTableMigration(modelPath)
|
|
149
|
-
}
|
|
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
|
-
}
|
|
175
|
-
async function createTableMigration(modelPath: string) {
|
|
176
|
-
log.debug('createTableMigration modelPath:', modelPath)
|
|
177
|
-
|
|
178
|
-
const model = (await import(modelPath)).default as Model
|
|
179
|
-
const tableName = getTableName(model, modelPath)
|
|
180
|
-
|
|
181
|
-
const twoFactorEnabled
|
|
182
|
-
= model.traits?.useAuth && typeof model.traits.useAuth !== 'boolean' ? model.traits.useAuth.useTwoFactor : false
|
|
183
|
-
|
|
184
|
-
await createPivotTableMigration(model, modelPath)
|
|
185
|
-
const otherModelRelations = await fetchOtherModelRelations(model, modelPath)
|
|
186
|
-
|
|
187
|
-
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
|
|
188
|
-
const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
|
|
189
|
-
|
|
190
|
-
const usePasskey = (typeof model.traits?.useAuth === 'object' && model.traits.useAuth.usePasskey) ?? false
|
|
191
|
-
|
|
192
|
-
if (usePasskey)
|
|
193
|
-
await createPasskeyMigration()
|
|
194
|
-
|
|
195
|
-
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
196
|
-
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
197
|
-
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
198
|
-
migrationContent += ` await db.schema\n`
|
|
199
|
-
migrationContent += ` .createTable('${tableName}')\n`
|
|
200
|
-
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())\n`
|
|
201
|
-
|
|
202
|
-
for (const [fieldName, options] of arrangeColumns(model.attributes)) {
|
|
203
|
-
const fieldOptions = options as Attribute
|
|
204
|
-
const fieldNameFormatted = snakeCase(fieldName)
|
|
205
|
-
const columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule, 'sqlite')
|
|
206
|
-
migrationContent += ` .addColumn('${fieldNameFormatted}', ${columnType}`
|
|
207
|
-
|
|
208
|
-
// Check if there are configurations that require the lambda function
|
|
209
|
-
if (fieldOptions.unique || fieldOptions?.required) {
|
|
210
|
-
migrationContent += `, col => col`
|
|
211
|
-
if (fieldOptions.unique)
|
|
212
|
-
migrationContent += `.unique()`
|
|
213
|
-
if (fieldOptions?.required)
|
|
214
|
-
migrationContent += `.notNull()`
|
|
215
|
-
migrationContent += ``
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
migrationContent += `)\n`
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (twoFactorEnabled !== false && twoFactorEnabled) {
|
|
222
|
-
migrationContent += ` .addColumn('two_factor_secret', 'varchar(255)')\n`
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (otherModelRelations?.length) {
|
|
226
|
-
for (const modelRelation of otherModelRelations) {
|
|
227
|
-
migrationContent += ` .addColumn('${modelRelation.foreignKey}', 'integer', (col) =>
|
|
228
|
-
col.references('${modelRelation.relationTable}.id').onDelete('cascade')
|
|
229
|
-
) \n`
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (usePasskey)
|
|
234
|
-
migrationContent += ` .addColumn('public_passkey', 'text')\n`
|
|
235
|
-
|
|
236
|
-
// Append created_at and updated_at columns if useTimestamps is true
|
|
237
|
-
if (useTimestamps) {
|
|
238
|
-
migrationContent
|
|
239
|
-
+= ' .addColumn(\'created_at\', \'timestamp\', col => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`))\n'
|
|
240
|
-
migrationContent += ' .addColumn(\'updated_at\', \'timestamp\')\n'
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (useSoftDeletes)
|
|
244
|
-
migrationContent += ` .addColumn('deleted_at', 'text')\n`
|
|
245
|
-
|
|
246
|
-
migrationContent += ` .execute()\n`
|
|
247
|
-
migrationContent += `}\n`
|
|
248
|
-
|
|
249
|
-
const timestamp = new Date().getTime().toString()
|
|
250
|
-
const migrationFileName = `${timestamp}-create-${tableName}-table.ts`
|
|
251
|
-
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
252
|
-
|
|
253
|
-
Bun.write(migrationFilePath, migrationContent)
|
|
254
|
-
|
|
255
|
-
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function createPivotTableMigration(model: Model, modelPath: string) {
|
|
259
|
-
const pivotTables = await getPivotTables(model, modelPath)
|
|
260
|
-
|
|
261
|
-
if (!pivotTables.length)
|
|
262
|
-
return
|
|
263
|
-
|
|
264
|
-
for (const pivotTable of pivotTables) {
|
|
265
|
-
const hasBeenMigrated = await checkPivotMigration(pivotTable.table)
|
|
266
|
-
|
|
267
|
-
if (hasBeenMigrated)
|
|
268
|
-
return
|
|
269
|
-
|
|
270
|
-
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
271
|
-
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
272
|
-
migrationContent += ` await db.schema\n`
|
|
273
|
-
migrationContent += ` .createTable('${pivotTable.table}')\n`
|
|
274
|
-
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())\n`
|
|
275
|
-
migrationContent += ` .addColumn('${pivotTable.firstForeignKey}', 'integer')\n`
|
|
276
|
-
migrationContent += ` .addColumn('${pivotTable.secondForeignKey}', 'integer')\n`
|
|
277
|
-
migrationContent += ` .execute()\n`
|
|
278
|
-
migrationContent += ` }\n`
|
|
279
|
-
|
|
280
|
-
const timestamp = new Date().getTime().toString()
|
|
281
|
-
const migrationFileName = `${timestamp}-create-${pivotTable.table}-table.ts`
|
|
282
|
-
|
|
283
|
-
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
284
|
-
|
|
285
|
-
Bun.write(migrationFilePath, migrationContent)
|
|
286
|
-
|
|
287
|
-
log.success(`Created pivot migration: ${migrationFileName}`)
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
async function createPasskeyMigration() {
|
|
292
|
-
const hasBeenMigrated = await hasTableBeenMigrated('users')
|
|
293
|
-
|
|
294
|
-
if (hasBeenMigrated)
|
|
295
|
-
return
|
|
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) {
|
|
327
|
-
const model = (await import(modelPath)).default as Model
|
|
328
|
-
const modelName = getModelName(model, modelPath)
|
|
329
|
-
const tableName = getTableName(model, modelPath)
|
|
330
|
-
let hasChanged = false
|
|
331
|
-
|
|
332
|
-
// Assuming you have a function to get the fields from the last migration
|
|
333
|
-
// For simplicity, this is not implemented here
|
|
334
|
-
const lastMigrationFields = await getLastMigrationFields(modelName)
|
|
335
|
-
const lastFields = lastMigrationFields ?? {}
|
|
336
|
-
const currentFields = model.attributes as Attributes
|
|
337
|
-
|
|
338
|
-
// Determine fields to add and remove
|
|
339
|
-
|
|
340
|
-
const changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields))
|
|
341
|
-
|
|
342
|
-
const fieldsToAdd = changes?.added || []
|
|
343
|
-
|
|
344
|
-
const fieldsToRemove = changes?.removed || []
|
|
345
|
-
|
|
346
|
-
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
347
|
-
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
348
|
-
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
349
|
-
|
|
350
|
-
if (fieldsToAdd.length || fieldsToRemove.length) {
|
|
351
|
-
hasChanged = true
|
|
352
|
-
migrationContent += ` await db.schema.alterTable('${tableName}')\n`
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const fieldValidations = findDifferingKeys(lastFields, currentFields)
|
|
356
|
-
|
|
357
|
-
for (const fieldValidation of fieldValidations) {
|
|
358
|
-
hasChanged = true
|
|
359
|
-
const fieldNameFormatted = snakeCase(fieldValidation.key)
|
|
360
|
-
migrationContent += `await sql\`
|
|
361
|
-
ALTER TABLE ${tableName}
|
|
362
|
-
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(${fieldValidation.max})
|
|
363
|
-
\`.execute(db)\n\n`
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Add new fields
|
|
367
|
-
for (const fieldName of fieldsToAdd) {
|
|
368
|
-
const options = currentFields[fieldName] as Attribute
|
|
369
|
-
const columnType = mapFieldTypeToColumnType(options.validation?.rule, 'sqlite')
|
|
370
|
-
const formattedFieldName = snakeCase(fieldName)
|
|
371
|
-
|
|
372
|
-
migrationContent += ` .addColumn('${formattedFieldName}', ${columnType}`
|
|
373
|
-
|
|
374
|
-
// Check if there are configurations that require the lambda function
|
|
375
|
-
if (options.unique || options?.required) {
|
|
376
|
-
migrationContent += `, col => col`
|
|
377
|
-
if (options.unique)
|
|
378
|
-
migrationContent += `.unique()`
|
|
379
|
-
if (options?.required)
|
|
380
|
-
migrationContent += `.notNull()`
|
|
381
|
-
migrationContent += ``
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
migrationContent += `)\n\n`
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Remove fields that no longer exist
|
|
388
|
-
for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
|
|
389
|
-
|
|
390
|
-
if (fieldsToAdd.length || fieldsToRemove.length)
|
|
391
|
-
migrationContent += ` .execute();\n`
|
|
392
|
-
|
|
393
|
-
const lastFieldOrder = Object.values(lastFields).map(attr => attr.order)
|
|
394
|
-
const currentFieldOrder = Object.values(currentFields).map(attr => attr.order)
|
|
395
|
-
|
|
396
|
-
if (!isArrayEqual(lastFieldOrder, currentFieldOrder)) {
|
|
397
|
-
hasChanged = true
|
|
398
|
-
migrationContent += reArrangeColumns(model.attributes, tableName)
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
migrationContent += `}\n`
|
|
402
|
-
|
|
403
|
-
const timestamp = new Date().getTime().toString()
|
|
404
|
-
const migrationFileName = `${timestamp}-update-${tableName}-table.ts`
|
|
405
|
-
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
406
|
-
|
|
407
|
-
if (hasChanged) {
|
|
408
|
-
Bun.write(migrationFilePath, migrationContent)
|
|
409
|
-
|
|
410
|
-
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function reArrangeColumns(attributes: Attributes | undefined, tableName: string): string {
|
|
415
|
-
const fields = arrangeColumns(attributes)
|
|
416
|
-
let migrationContent = ''
|
|
417
|
-
|
|
418
|
-
let previousField = ''
|
|
419
|
-
for (const [fieldName] of fields) {
|
|
420
|
-
const fieldNameFormatted = snakeCase(fieldName)
|
|
421
|
-
|
|
422
|
-
if (previousField) {
|
|
423
|
-
migrationContent += `await sql\`
|
|
424
|
-
ALTER TABLE ${tableName}
|
|
425
|
-
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
|
|
426
|
-
\`.execute(db)\n\n`
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
previousField = fieldNameFormatted
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return migrationContent
|
|
433
|
-
}
|
package/src/index.ts
DELETED
package/src/migrations.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { $ } from 'bun'
|
|
2
|
-
import { log } from '@stacksjs/cli'
|
|
3
|
-
import { database } from '@stacksjs/config'
|
|
4
|
-
import { type Err, err, handleError, type Ok, ok, type Result } from '@stacksjs/error-handling'
|
|
5
|
-
import { path } from '@stacksjs/path'
|
|
6
|
-
import { fs, globSync } from '@stacksjs/storage'
|
|
7
|
-
import { FileMigrationProvider, type MigrationResult, Migrator } from 'kysely'
|
|
8
|
-
import { generateMysqlMigration, generatePostgresMigration, generateSqliteMigration, resetMysqlDatabase, resetPostgresDatabase, resetSqliteDatabase } from './drivers'
|
|
9
|
-
import { db } from './utils'
|
|
10
|
-
|
|
11
|
-
const driver = database.default || ''
|
|
12
|
-
|
|
13
|
-
export const migrator: Migrator = new Migrator({
|
|
14
|
-
db,
|
|
15
|
-
|
|
16
|
-
provider: new FileMigrationProvider({
|
|
17
|
-
fs,
|
|
18
|
-
path,
|
|
19
|
-
// This needs to be an absolute path.
|
|
20
|
-
migrationFolder: path.userMigrationsPath(),
|
|
21
|
-
}),
|
|
22
|
-
|
|
23
|
-
migrationTableName: database.migrations,
|
|
24
|
-
migrationLockTableName: database.migrationLocks,
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
// const migratorForeign = new Migrator({
|
|
28
|
-
// db,
|
|
29
|
-
|
|
30
|
-
// provider: new FileMigrationProvider({
|
|
31
|
-
// fs,
|
|
32
|
-
// path,
|
|
33
|
-
// // This needs to be an absolute path.
|
|
34
|
-
// migrationFolder: path.userMigrationsPath('foreign'),
|
|
35
|
-
// }),
|
|
36
|
-
// })
|
|
37
|
-
|
|
38
|
-
export async function runDatabaseMigration(): Promise<Result<MigrationResult[] | string, Error>> {
|
|
39
|
-
try {
|
|
40
|
-
log.info('Migrating database...')
|
|
41
|
-
|
|
42
|
-
const { error, results } = await migrator.migrateToLatest()
|
|
43
|
-
|
|
44
|
-
if (error) {
|
|
45
|
-
return err(handleError(error))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (results?.length === 0) {
|
|
49
|
-
log.success('No new migrations were executed')
|
|
50
|
-
return ok('No new migrations were executed')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (results)
|
|
54
|
-
return ok(results)
|
|
55
|
-
|
|
56
|
-
log.success('Database migration completed with no new migrations.')
|
|
57
|
-
return ok('Database migration completed with no new migrations.')
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
return err(handleError('Migration failed', error))
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface MigrationOptions {
|
|
65
|
-
name: string
|
|
66
|
-
up: string
|
|
67
|
-
}
|
|
68
|
-
|
|
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()
|
|
76
|
-
|
|
77
|
-
throw new Error('Unsupported database driver in resetDatabase')
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function generateMigrations(): Promise<Ok<string, never> | Err<string, any>> {
|
|
81
|
-
try {
|
|
82
|
-
log.info('Generating migrations...')
|
|
83
|
-
|
|
84
|
-
const modelFiles = globSync([path.userModelsPath('*.ts')], { absolute: true })
|
|
85
|
-
|
|
86
|
-
for (const file of modelFiles) {
|
|
87
|
-
log.debug('Generating migration for:', file)
|
|
88
|
-
|
|
89
|
-
await generateMigration(file)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
log.success('Migrations generated')
|
|
93
|
-
return ok('Migrations generated')
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
return err(error)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function generateMigration(modelPath: string): Promise<void> {
|
|
101
|
-
if (driver === 'sqlite')
|
|
102
|
-
await generateSqliteMigration(modelPath)
|
|
103
|
-
|
|
104
|
-
if (driver === 'mysql')
|
|
105
|
-
await generateMysqlMigration(modelPath)
|
|
106
|
-
|
|
107
|
-
if (driver === 'postgres')
|
|
108
|
-
await generatePostgresMigration(modelPath)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export async function haveModelFieldsChangedSinceLastMigration(modelPath: string): Promise<boolean> {
|
|
112
|
-
log.debug(`haveModelFieldsChangedSinceLastMigration for model: ${modelPath}`)
|
|
113
|
-
|
|
114
|
-
// const model = await import(modelPath)
|
|
115
|
-
// const tableName = model.default.table
|
|
116
|
-
// const lastMigration = await lastMigrationDate()
|
|
117
|
-
|
|
118
|
-
// now that we know the date, we need to check the git history for changes to the model file since that date
|
|
119
|
-
const cmd = ``
|
|
120
|
-
const gitHistory = await $`${cmd}`.text()
|
|
121
|
-
|
|
122
|
-
// if there are updates, then we need to check whether
|
|
123
|
-
// the updates include the any updates to the model
|
|
124
|
-
// fields that would require a migration
|
|
125
|
-
|
|
126
|
-
return !!gitHistory
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export async function lastMigration(): Promise<any> {
|
|
130
|
-
try {
|
|
131
|
-
return await db.selectFrom('migrations').selectAll().orderBy('timestamp', 'desc').limit(1).execute()
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
console.error('Failed to get last migration:', error)
|
|
135
|
-
return { error }
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export async function lastMigrationDate(): Promise<string | undefined> {
|
|
140
|
-
try {
|
|
141
|
-
return (await db.selectFrom('migrations').select('timestamp').orderBy('timestamp', 'desc').limit(1).execute())[0]
|
|
142
|
-
.timestamp
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
console.error('Failed to get last migration date:', error)
|
|
146
|
-
return undefined
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export type { MigrationResult }
|
package/src/schema.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { log } from '@stacksjs/logging'
|
|
2
|
-
import { Table } from './table'
|
|
3
|
-
|
|
4
|
-
export const Schema = {
|
|
5
|
-
async createTable(tableName: string, callback: (table: Table) => void): Promise<void> {
|
|
6
|
-
const table = new Table()
|
|
7
|
-
|
|
8
|
-
callback(table)
|
|
9
|
-
|
|
10
|
-
table.execute() // Simulate the execution of the table creation
|
|
11
|
-
|
|
12
|
-
log.success(`Table "${tableName}" created.`)
|
|
13
|
-
},
|
|
14
|
-
}
|