@stacksjs/database 0.59.11 → 0.61.1
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 +59473 -0
- package/package.json +11 -6
- package/src/column.ts +6 -2
- package/src/drivers/index.ts +226 -0
- package/src/drivers/mysql.ts +276 -0
- package/src/drivers/postgres.ts +277 -0
- package/src/drivers/sqlite.ts +257 -0
- package/src/index.ts +1 -63
- package/src/migrations.ts +156 -57
- package/src/schema.ts +8 -5
- package/src/seeder.ts +167 -77
- package/src/table.ts +8 -5
- package/src/types.ts +3 -1
- package/src/utils.ts +50 -1
- package/dist/column.d.ts +0 -18
- package/dist/index.d.ts +0 -19
- package/dist/migrations.d.ts +0 -6
- package/dist/schema.d.ts +0 -4
- package/dist/table.d.ts +0 -8
- package/dist/types.d.ts +0 -1
- package/dist/utils.d.ts +0 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { italic, log } from '@stacksjs/cli'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { ok } from '@stacksjs/error-handling'
|
|
4
|
+
import { modelTableName } from '@stacksjs/orm'
|
|
5
|
+
import { path } from '@stacksjs/path'
|
|
6
|
+
import { fs, glob } from '@stacksjs/storage'
|
|
7
|
+
import type { Attribute, Attributes, Model } from '@stacksjs/types'
|
|
8
|
+
import {
|
|
9
|
+
checkPivotMigration,
|
|
10
|
+
fetchOtherModelRelations,
|
|
11
|
+
getLastMigrationFields,
|
|
12
|
+
getPivotTables,
|
|
13
|
+
hasTableBeenMigrated,
|
|
14
|
+
mapFieldTypeToColumnType,
|
|
15
|
+
} from '.'
|
|
16
|
+
|
|
17
|
+
export async function resetPostgresDatabase() {
|
|
18
|
+
const tables = await fetchMysqlTables()
|
|
19
|
+
|
|
20
|
+
for (const table of tables) await db.schema.dropTable(table).ifExists().execute()
|
|
21
|
+
|
|
22
|
+
await db.schema.dropTable('migrations').ifExists().execute()
|
|
23
|
+
await db.schema.dropTable('migration_locks').ifExists().execute()
|
|
24
|
+
|
|
25
|
+
const files = await fs.readdir(path.userMigrationsPath())
|
|
26
|
+
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
27
|
+
|
|
28
|
+
const userModelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
29
|
+
|
|
30
|
+
for (const userModel of userModelFiles) {
|
|
31
|
+
const userModelPath = await import(userModel)
|
|
32
|
+
|
|
33
|
+
const pivotTables = await getPivotTables(userModelPath)
|
|
34
|
+
|
|
35
|
+
for (const pivotTable of pivotTables) await db.schema.dropTable(pivotTable.table).ifExists().execute()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (modelFiles.length) {
|
|
39
|
+
for (const modelFile of modelFiles) {
|
|
40
|
+
if (modelFile.endsWith('.ts')) {
|
|
41
|
+
const modelPath = path.frameworkPath(`database/models/${modelFile}`)
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(modelPath)) await Bun.$`rm ${modelPath}`
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (files.length) {
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
if (file.endsWith('.ts')) {
|
|
51
|
+
const migrationPath = path.userMigrationsPath(`${file}`)
|
|
52
|
+
|
|
53
|
+
if (fs.existsSync(migrationPath)) await Bun.$`rm ${migrationPath}`
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return ok('All tables dropped successfully!')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function generatePostgresMigration(modelPath: string) {
|
|
62
|
+
// check if any files are in the database folder
|
|
63
|
+
const files = await fs.readdir(path.userMigrationsPath())
|
|
64
|
+
|
|
65
|
+
if (files.length === 0) {
|
|
66
|
+
log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
|
|
67
|
+
|
|
68
|
+
// delete the *.ts files in the database/models folder
|
|
69
|
+
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
70
|
+
|
|
71
|
+
if (modelFiles.length) {
|
|
72
|
+
log.debug('No existing model files in framework path...')
|
|
73
|
+
|
|
74
|
+
for (const file of modelFiles) {
|
|
75
|
+
if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const model = (await import(modelPath)).default as Model
|
|
81
|
+
const fileName = path.basename(modelPath)
|
|
82
|
+
const tableName = await modelTableName(model)
|
|
83
|
+
|
|
84
|
+
const fieldsString = JSON.stringify(model.attributes, null, 2) // Pretty print the JSON
|
|
85
|
+
const copiedModelPath = path.frameworkPath(`database/models/${fileName}`)
|
|
86
|
+
|
|
87
|
+
let haveFieldsChanged = false
|
|
88
|
+
|
|
89
|
+
// if the file exists, we need to check if the fields have changed
|
|
90
|
+
if (fs.existsSync(copiedModelPath)) {
|
|
91
|
+
log.info(`Fields have already been generated for ${tableName}`)
|
|
92
|
+
|
|
93
|
+
const previousFields = await getLastMigrationFields(fileName)
|
|
94
|
+
const previousFieldsString = JSON.stringify(previousFields, null, 2) // Convert to string for comparison
|
|
95
|
+
|
|
96
|
+
if (previousFieldsString === fieldsString) {
|
|
97
|
+
log.debug(`Fields have not changed for ${tableName}`)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
haveFieldsChanged = true
|
|
102
|
+
log.debug(`Fields have changed for ${tableName}`)
|
|
103
|
+
} else {
|
|
104
|
+
log.debug(`Fields have not been generated for ${tableName}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// store the fields of the model to a file
|
|
108
|
+
await Bun.$`cp ${modelPath} ${copiedModelPath}`
|
|
109
|
+
|
|
110
|
+
// if the fields have changed, we need to create a new update migration
|
|
111
|
+
// if the fields have not changed, we need to migrate the table
|
|
112
|
+
|
|
113
|
+
// we need to check if this tableName has already been migrated
|
|
114
|
+
const hasBeenMigrated = await hasTableBeenMigrated(tableName)
|
|
115
|
+
|
|
116
|
+
log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`)
|
|
117
|
+
|
|
118
|
+
if (haveFieldsChanged) await createAlterTableMigration(modelPath)
|
|
119
|
+
else await createTableMigration(modelPath)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function createTableMigration(modelPath: string) {
|
|
123
|
+
log.debug('createTableMigration modelPath:', modelPath)
|
|
124
|
+
|
|
125
|
+
const model = (await import(modelPath)).default as Model
|
|
126
|
+
const tableName = await modelTableName(model)
|
|
127
|
+
|
|
128
|
+
await createPivotTableMigration(model)
|
|
129
|
+
|
|
130
|
+
const otherModelRelations = await fetchOtherModelRelations(model)
|
|
131
|
+
const fields = model.attributes
|
|
132
|
+
const useTimestamps = model.traits?.useTimestamps ?? model.traits?.timestampable ?? true
|
|
133
|
+
const useSoftDeletes = model.traits?.useSoftDeletes ?? model.traits?.softDeletable ?? false
|
|
134
|
+
|
|
135
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
136
|
+
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
137
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
138
|
+
migrationContent += ` await db.schema\n`
|
|
139
|
+
migrationContent += ` .createTable('${tableName}')\n`
|
|
140
|
+
migrationContent += ` .addColumn('id', 'serial', (col) => col.primaryKey())\n`
|
|
141
|
+
|
|
142
|
+
for (const [fieldName, options] of Object.entries(fields)) {
|
|
143
|
+
const fieldOptions = options as Attribute
|
|
144
|
+
const columnType = mapFieldTypeToColumnType(fieldOptions.validator?.rule)
|
|
145
|
+
migrationContent += ` .addColumn('${fieldName}', '${columnType}'`
|
|
146
|
+
|
|
147
|
+
// Check if there are configurations that require the lambda function
|
|
148
|
+
if (fieldOptions.unique || fieldOptions.validator?.rule?.required) {
|
|
149
|
+
migrationContent += `, col => col`
|
|
150
|
+
if (fieldOptions.unique) migrationContent += `.unique()`
|
|
151
|
+
if (fieldOptions.validator?.rule?.required) migrationContent += `.notNull()`
|
|
152
|
+
migrationContent += ``
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
migrationContent += `)\n`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (otherModelRelations?.length) {
|
|
159
|
+
for (const modelRelation of otherModelRelations) {
|
|
160
|
+
migrationContent += ` .addColumn('${modelRelation.foreignKey}', 'integer', (col) =>
|
|
161
|
+
col.references('${modelRelation.relationTable}.id').onDelete('cascade').notNull()
|
|
162
|
+
) \n`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Append created_at and updated_at columns if useTimestamps is true
|
|
167
|
+
if (useTimestamps) {
|
|
168
|
+
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
|
|
169
|
+
migrationContent += ` .addColumn('updated_at', 'timestamp')\n`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Append deleted_at column if useSoftDeletes is true
|
|
173
|
+
if (useSoftDeletes) migrationContent += ` .addColumn('deleted_at', 'timestamp')\n`
|
|
174
|
+
|
|
175
|
+
migrationContent += ` .execute()\n`
|
|
176
|
+
migrationContent += `}\n`
|
|
177
|
+
|
|
178
|
+
const timestamp = new Date().getTime().toString()
|
|
179
|
+
const migrationFileName = `${timestamp}-create-${tableName}-table.ts`
|
|
180
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
181
|
+
|
|
182
|
+
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
183
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
184
|
+
|
|
185
|
+
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function createPivotTableMigration(model: any) {
|
|
189
|
+
const pivotTables = await getPivotTables(model)
|
|
190
|
+
|
|
191
|
+
if (!pivotTables.length) return
|
|
192
|
+
for (const pivotTable of pivotTables) {
|
|
193
|
+
const hasBeenMigrated = await checkPivotMigration(pivotTable.table)
|
|
194
|
+
|
|
195
|
+
if (hasBeenMigrated) return
|
|
196
|
+
|
|
197
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
198
|
+
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
199
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
200
|
+
migrationContent += ` await db.schema\n`
|
|
201
|
+
migrationContent += ` .createTable('${pivotTable.table}')\n`
|
|
202
|
+
migrationContent += ` .addColumn('id', 'serial', (col) => col.primaryKey())\n`
|
|
203
|
+
migrationContent += ` .addColumn('user_id', 'integer')\n`
|
|
204
|
+
migrationContent += ` .addColumn('subscriber_id', 'integer')\n`
|
|
205
|
+
migrationContent += ` .execute()\n`
|
|
206
|
+
migrationContent += ` }\n`
|
|
207
|
+
|
|
208
|
+
const timestamp = new Date().getTime().toString()
|
|
209
|
+
const migrationFileName = `${timestamp}-create-${pivotTable.table}-table.ts`
|
|
210
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
211
|
+
|
|
212
|
+
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
213
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
214
|
+
|
|
215
|
+
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function createAlterTableMigration(modelPath: string) {
|
|
220
|
+
console.log('createAlterTableMigration')
|
|
221
|
+
|
|
222
|
+
const model = (await import(modelPath)).default as Model
|
|
223
|
+
const modelName = path.basename(modelPath)
|
|
224
|
+
const tableName = await modelTableName(model)
|
|
225
|
+
|
|
226
|
+
// Assuming you have a function to get the fields from the last migration
|
|
227
|
+
// For simplicity, this is not implemented here
|
|
228
|
+
const lastMigrationFields = await getLastMigrationFields(modelName)
|
|
229
|
+
const lastFields = lastMigrationFields ?? {}
|
|
230
|
+
const currentFields = model.attributes
|
|
231
|
+
|
|
232
|
+
// Determine fields to add and remove
|
|
233
|
+
const fieldsToAdd = Object.keys(currentFields)
|
|
234
|
+
const fieldsToRemove = Object.keys(lastFields)
|
|
235
|
+
|
|
236
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
237
|
+
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
238
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
239
|
+
migrationContent += ` await db.schema.alterTable('${tableName}')\n`
|
|
240
|
+
|
|
241
|
+
// Add new fields
|
|
242
|
+
for (const fieldName of fieldsToAdd) {
|
|
243
|
+
const options = currentFields[fieldName] as Attribute
|
|
244
|
+
const columnType = mapFieldTypeToColumnType(options.validator?.rule)
|
|
245
|
+
migrationContent += ` .addColumn('${fieldName}', '${columnType}')\n`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Remove fields that no longer exist
|
|
249
|
+
for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
|
|
250
|
+
|
|
251
|
+
migrationContent += ` .execute();\n`
|
|
252
|
+
migrationContent += `}\n`
|
|
253
|
+
|
|
254
|
+
const timestamp = new Date().getTime().toString()
|
|
255
|
+
const migrationFileName = `${timestamp}-update-${tableName}-table.ts`
|
|
256
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
257
|
+
|
|
258
|
+
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
259
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
260
|
+
|
|
261
|
+
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function fetchMysqlTables(): Promise<string[]> {
|
|
265
|
+
const modelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
266
|
+
const tables: string[] = []
|
|
267
|
+
|
|
268
|
+
for (const modelPath of modelFiles) {
|
|
269
|
+
const model = await import(modelPath)
|
|
270
|
+
|
|
271
|
+
const tableName = await modelTableName(model)
|
|
272
|
+
|
|
273
|
+
tables.push(tableName)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return tables
|
|
277
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { italic, log } from '@stacksjs/cli'
|
|
2
|
+
import { db } from '@stacksjs/database'
|
|
3
|
+
import { ok } from '@stacksjs/error-handling'
|
|
4
|
+
import { modelTableName } from '@stacksjs/orm'
|
|
5
|
+
import { path } from '@stacksjs/path'
|
|
6
|
+
import { fs, glob } from '@stacksjs/storage'
|
|
7
|
+
import type { Attribute, Attributes, Model } from '@stacksjs/types'
|
|
8
|
+
import {
|
|
9
|
+
checkPivotMigration,
|
|
10
|
+
fetchOtherModelRelations,
|
|
11
|
+
getLastMigrationFields,
|
|
12
|
+
getPivotTables,
|
|
13
|
+
hasTableBeenMigrated,
|
|
14
|
+
mapFieldTypeToColumnType,
|
|
15
|
+
} from '.'
|
|
16
|
+
|
|
17
|
+
export async function resetSqliteDatabase() {
|
|
18
|
+
const dbPath = path.userDatabasePath('stacks.sqlite')
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(dbPath)) await Bun.$`rm ${dbPath}`
|
|
21
|
+
|
|
22
|
+
const files = await fs.readdir(path.userMigrationsPath())
|
|
23
|
+
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
24
|
+
|
|
25
|
+
const userModelFiles = glob.sync(path.userModelsPath('*.ts'))
|
|
26
|
+
|
|
27
|
+
for (const userModel of userModelFiles) {
|
|
28
|
+
const userModelPath = await import(userModel)
|
|
29
|
+
|
|
30
|
+
const pivotTables = await getPivotTables(userModelPath)
|
|
31
|
+
|
|
32
|
+
for (const pivotTable of pivotTables) await db.schema.dropTable(pivotTable.table).ifExists().execute()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (modelFiles.length) {
|
|
36
|
+
for (const modelFile of modelFiles) {
|
|
37
|
+
if (modelFile.endsWith('.ts')) {
|
|
38
|
+
const modelPath = path.frameworkPath(`database/models/${modelFile}`)
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(modelPath)) await Bun.$`rm ${modelPath}`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (files.length) {
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
if (file.endsWith('.ts')) {
|
|
48
|
+
const migrationPath = path.userMigrationsPath(`${file}`)
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(migrationPath)) await Bun.$`rm ${migrationPath}`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return ok('All tables dropped successfully!')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function generateSqliteMigration(modelPath: string) {
|
|
59
|
+
// check if any files are in the database folder
|
|
60
|
+
const files = await fs.readdir(path.userMigrationsPath())
|
|
61
|
+
|
|
62
|
+
if (files.length === 0) {
|
|
63
|
+
log.debug('No migrations found in the database folder, deleting all framework/database/*.json files...')
|
|
64
|
+
|
|
65
|
+
// delete the *.ts files in the database/models folder
|
|
66
|
+
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
|
|
67
|
+
|
|
68
|
+
if (modelFiles.length) {
|
|
69
|
+
log.debug('No existing model files in framework path...')
|
|
70
|
+
|
|
71
|
+
for (const file of modelFiles)
|
|
72
|
+
if (file.endsWith('.ts')) await fs.unlink(path.frameworkPath(`database/models/${file}`))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const model = (await import(modelPath)).default as Model
|
|
77
|
+
const fileName = path.basename(modelPath)
|
|
78
|
+
const tableName = await modelTableName(model)
|
|
79
|
+
|
|
80
|
+
const fieldsString = JSON.stringify(model.attributes, null, 2) // Pretty print the JSON
|
|
81
|
+
const copiedModelPath = path.frameworkPath(`database/models/${fileName}`)
|
|
82
|
+
|
|
83
|
+
let haveFieldsChanged = false
|
|
84
|
+
|
|
85
|
+
// if the file exists, we need to check if the fields have changed
|
|
86
|
+
if (fs.existsSync(copiedModelPath)) {
|
|
87
|
+
log.debug(`Fields have already been generated for ${tableName}`)
|
|
88
|
+
|
|
89
|
+
const previousFields = await getLastMigrationFields(fileName)
|
|
90
|
+
const previousFieldsString = JSON.stringify(previousFields, null, 2) // Convert to string for comparison
|
|
91
|
+
|
|
92
|
+
if (previousFieldsString === fieldsString) {
|
|
93
|
+
log.debug(`Fields have not changed for ${tableName}`)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
haveFieldsChanged = true
|
|
98
|
+
log.debug(`Fields have changed for ${tableName}`)
|
|
99
|
+
} else {
|
|
100
|
+
log.debug(`Fields have not been generated for ${tableName}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// store the fields of the model to a file
|
|
104
|
+
await Bun.$`cp ${modelPath} ${copiedModelPath}`
|
|
105
|
+
|
|
106
|
+
// if the fields have changed, we need to create a new update migration
|
|
107
|
+
// if the fields have not changed, we need to migrate the table
|
|
108
|
+
|
|
109
|
+
// we need to check if this tableName has already been migrated
|
|
110
|
+
const hasBeenMigrated = await hasTableBeenMigrated(tableName)
|
|
111
|
+
|
|
112
|
+
log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`)
|
|
113
|
+
|
|
114
|
+
if (haveFieldsChanged) await createAlterTableMigration(modelPath)
|
|
115
|
+
else await createTableMigration(modelPath)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function createTableMigration(modelPath: string): Promise<void> {
|
|
119
|
+
log.debug('createTableMigration modelPath:', modelPath)
|
|
120
|
+
|
|
121
|
+
const model = (await import(modelPath)).default as Model
|
|
122
|
+
const tableName = await modelTableName(model)
|
|
123
|
+
|
|
124
|
+
await createPivotTableMigration(model)
|
|
125
|
+
const otherModelRelations = await fetchOtherModelRelations(model)
|
|
126
|
+
const fields = model.attributes
|
|
127
|
+
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? true
|
|
128
|
+
const useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? false
|
|
129
|
+
|
|
130
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
131
|
+
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
132
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
133
|
+
migrationContent += ` await db.schema\n`
|
|
134
|
+
migrationContent += ` .createTable('${tableName}')\n`
|
|
135
|
+
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())\n`
|
|
136
|
+
|
|
137
|
+
for (const [fieldName, options] of Object.entries(fields)) {
|
|
138
|
+
const fieldOptions = options as Attribute
|
|
139
|
+
const columnType = mapFieldTypeToColumnType(fieldOptions.validator?.rule)
|
|
140
|
+
migrationContent += ` .addColumn('${fieldName}', '${columnType}'`
|
|
141
|
+
|
|
142
|
+
// Check if there are configurations that require the lambda function
|
|
143
|
+
if (fieldOptions.unique || fieldOptions.validator?.rule?.required) {
|
|
144
|
+
migrationContent += `, col => col`
|
|
145
|
+
if (fieldOptions.unique) migrationContent += `.unique()`
|
|
146
|
+
if (fieldOptions.validator?.rule?.required) migrationContent += `.notNull()`
|
|
147
|
+
migrationContent += ``
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
migrationContent += `)\n`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (otherModelRelations?.length) {
|
|
154
|
+
for (const modelRelation of otherModelRelations) {
|
|
155
|
+
migrationContent += ` .addColumn('${modelRelation.foreignKey}', 'integer', (col) =>
|
|
156
|
+
col.references('${modelRelation.relationTable}.id').onDelete('cascade').notNull()
|
|
157
|
+
) \n`
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Append created_at and updated_at columns if useTimestamps is true
|
|
162
|
+
if (useTimestamps) {
|
|
163
|
+
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))\n`
|
|
164
|
+
migrationContent += ` .addColumn('updated_at', 'timestamp')\n`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Append deleted_at column if useSoftDeletes is true
|
|
168
|
+
if (useSoftDeletes) migrationContent += ` .addColumn('deleted_at', 'timestamp')\n`
|
|
169
|
+
|
|
170
|
+
migrationContent += ` .execute()\n`
|
|
171
|
+
migrationContent += `}\n`
|
|
172
|
+
|
|
173
|
+
const timestamp = new Date().getTime().toString()
|
|
174
|
+
const migrationFileName = `${timestamp}-create-${tableName}-table.ts`
|
|
175
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
176
|
+
|
|
177
|
+
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
178
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
179
|
+
|
|
180
|
+
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function createPivotTableMigration(model: Model) {
|
|
184
|
+
const pivotTables = await getPivotTables(model)
|
|
185
|
+
|
|
186
|
+
if (!pivotTables.length) return
|
|
187
|
+
|
|
188
|
+
for (const pivotTable of pivotTables) {
|
|
189
|
+
const hasBeenMigrated = await checkPivotMigration(pivotTable.table)
|
|
190
|
+
|
|
191
|
+
if (hasBeenMigrated) return
|
|
192
|
+
|
|
193
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
194
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
195
|
+
migrationContent += ` await db.schema\n`
|
|
196
|
+
migrationContent += ` .createTable('${pivotTable.table}')\n`
|
|
197
|
+
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())\n`
|
|
198
|
+
migrationContent += ` .addColumn('${pivotTable.firstForeignKey}', 'integer')\n`
|
|
199
|
+
migrationContent += ` .addColumn('${pivotTable.secondForeignKey}', 'integer')\n`
|
|
200
|
+
migrationContent += ` .execute()\n`
|
|
201
|
+
migrationContent += ` }\n`
|
|
202
|
+
|
|
203
|
+
const timestamp = new Date().getTime().toString()
|
|
204
|
+
const migrationFileName = `${timestamp}-create-${pivotTable.table}-table.ts`
|
|
205
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
206
|
+
|
|
207
|
+
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
208
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
209
|
+
|
|
210
|
+
log.success(`Created pivot migration: ${migrationFileName}`)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function createAlterTableMigration(modelPath: string) {
|
|
215
|
+
console.log('createAlterTableMigration')
|
|
216
|
+
|
|
217
|
+
const model = (await import(modelPath)).default as Model
|
|
218
|
+
const modelName = path.basename(modelPath)
|
|
219
|
+
const tableName = await modelTableName(model)
|
|
220
|
+
|
|
221
|
+
// Assuming you have a function to get the fields from the last migration
|
|
222
|
+
// For simplicity, this is not implemented here
|
|
223
|
+
const lastMigrationFields = await getLastMigrationFields(modelName)
|
|
224
|
+
const lastFields = lastMigrationFields ?? {}
|
|
225
|
+
const currentFields = model.attributes as Attributes
|
|
226
|
+
|
|
227
|
+
// Determine fields to add and remove
|
|
228
|
+
const fieldsToAdd = Object.keys(currentFields)
|
|
229
|
+
const fieldsToRemove = Object.keys(lastFields)
|
|
230
|
+
|
|
231
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
|
|
232
|
+
migrationContent += `import { sql } from '@stacksjs/database'\n\n`
|
|
233
|
+
migrationContent += `export async function up(db: Database<any>) {\n`
|
|
234
|
+
migrationContent += ` await db.schema.alterTable('${tableName}')\n`
|
|
235
|
+
|
|
236
|
+
// Add new fields
|
|
237
|
+
for (const fieldName of fieldsToAdd) {
|
|
238
|
+
const options = currentFields[fieldName] as Attribute
|
|
239
|
+
const columnType = mapFieldTypeToColumnType(options.validator?.rule)
|
|
240
|
+
migrationContent += ` .addColumn('${fieldName}', '${columnType}')\n`
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Remove fields that no longer exist
|
|
244
|
+
for (const fieldName of fieldsToRemove) migrationContent += ` .dropColumn('${fieldName}')\n`
|
|
245
|
+
|
|
246
|
+
migrationContent += ` .execute();\n`
|
|
247
|
+
migrationContent += `}\n`
|
|
248
|
+
|
|
249
|
+
const timestamp = new Date().getTime().toString()
|
|
250
|
+
const migrationFileName = `${timestamp}-update-${tableName}-table.ts`
|
|
251
|
+
const migrationFilePath = path.userMigrationsPath(migrationFileName)
|
|
252
|
+
|
|
253
|
+
// Assuming fs.writeFileSync is available or use an equivalent method
|
|
254
|
+
Bun.write(migrationFilePath, migrationContent)
|
|
255
|
+
|
|
256
|
+
log.success(`Created migration: ${italic(migrationFileName)}`)
|
|
257
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,67 +1,5 @@
|
|
|
1
|
-
// export * from './seeder''
|
|
2
|
-
import { Kysely, MysqlDialect } from 'kysely'
|
|
3
|
-
import { createPool } from 'mysql2'
|
|
4
|
-
import type { ColumnType, Generated } from 'kysely'
|
|
5
|
-
import { BunWorkerDialect } from './kysely-bun-worker'
|
|
6
|
-
|
|
7
1
|
export * from './schema'
|
|
8
2
|
export * from './migrations'
|
|
3
|
+
export * from './seeder'
|
|
9
4
|
export * from './types'
|
|
10
5
|
export * from './utils'
|
|
11
|
-
|
|
12
|
-
// const driver = config.database.default
|
|
13
|
-
const driver = 'mysql'
|
|
14
|
-
|
|
15
|
-
// const dbName = config.database.name
|
|
16
|
-
|
|
17
|
-
export interface UsersTable {
|
|
18
|
-
// Columns that are generated by the database should be marked
|
|
19
|
-
// using the `Generated` type. This way they are automatically
|
|
20
|
-
// made optional in inserts and updates.
|
|
21
|
-
id: Generated<number>
|
|
22
|
-
|
|
23
|
-
name: string
|
|
24
|
-
email: string
|
|
25
|
-
|
|
26
|
-
// If the column is nullable in the database, make its type nullable.
|
|
27
|
-
// Don't use optional properties. Optionality is always determined
|
|
28
|
-
// automatically by Kysely.
|
|
29
|
-
password: string
|
|
30
|
-
|
|
31
|
-
// You can specify a different type for each operation (select, insert and
|
|
32
|
-
// update) using the `ColumnType<SelectType, InsertType, UpdateType>`
|
|
33
|
-
// wrapper. Here we define a column `created_at` that is selected as
|
|
34
|
-
// a `Date`, can optionally be provided as a `string` in inserts and
|
|
35
|
-
// can never be updated:
|
|
36
|
-
created_at: ColumnType<Date, string | undefined, never>
|
|
37
|
-
deleted_at: ColumnType<Date, string | undefined, never>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface Database {
|
|
41
|
-
users: UsersTable
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let dialect
|
|
45
|
-
|
|
46
|
-
if (driver === 'sqlite') {
|
|
47
|
-
dialect = new BunWorkerDialect({
|
|
48
|
-
url: 'stacks.sqlite',
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
dialect = new MysqlDialect({
|
|
53
|
-
pool: createPool({
|
|
54
|
-
database: 'stacks',
|
|
55
|
-
host: '127.0.0.1',
|
|
56
|
-
user: 'root',
|
|
57
|
-
password: '',
|
|
58
|
-
port: 3306,
|
|
59
|
-
}),
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const db = new Kysely<Database>({
|
|
64
|
-
dialect,
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
export const dbDialect = dialect
|