@stacksjs/database 0.70.258 → 0.70.260
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/auth-tables.js +18 -137
- package/dist/column.js +1 -26
- package/dist/custom/audits.js +20 -54
- package/dist/custom/errors.js +16 -46
- package/dist/custom/index.js +1 -3
- package/dist/custom/jobs.js +13 -137
- package/dist/database.js +1 -181
- package/dist/datetime-columns.js +2 -79
- package/dist/ddl-constraints.js +7 -111
- package/dist/defaults.js +1 -48
- package/dist/dialect.js +1 -79
- package/dist/driver-config.js +1 -172
- package/dist/drivers/defaults/index.js +1 -1
- package/dist/drivers/defaults/traits.js +1 -29
- package/dist/drivers/dynamodb.js +1 -607
- package/dist/drivers/helpers.js +1 -206
- package/dist/drivers/index.js +1 -9
- package/dist/drivers/mysql.js +58 -299
- package/dist/drivers/postgres.js +78 -368
- package/dist/drivers/sqlite.js +61 -379
- package/dist/ensure-database.js +1 -145
- package/dist/fk-audit.js +3 -187
- package/dist/index.js +1 -64
- package/dist/managed-columns.js +1 -59
- package/dist/migration-dialect.js +4 -107
- package/dist/migration-ledger.js +1 -382
- package/dist/migration-lock.js +1 -143
- package/dist/migrations.js +15 -1118
- package/dist/model-sources.js +1 -76
- package/dist/notification-tables.js +4 -49
- package/dist/query-logger.js +2 -241
- package/dist/query-parser.js +1 -93
- package/dist/rbac-tables.js +6 -61
- package/dist/relation-columns.js +1 -66
- package/dist/replicas.js +1 -74
- package/dist/safe-migrations.js +2 -52
- package/dist/schema.js +1 -10
- package/dist/seeder.js +1 -457
- package/dist/sql-helpers.js +1 -50
- package/dist/table.js +1 -26
- package/dist/tools/setup.js +1 -6
- package/dist/trait-tables.js +8 -153
- package/dist/transaction-context.js +1 -62
- package/dist/types.js +1 -98
- package/dist/unique-audit.js +3 -155
- package/dist/utils.js +1 -285
- package/dist/uuid-columns.js +1 -68
- package/dist/validators.js +1 -122
- package/dist/vschema.js +2 -121
- package/package.json +20 -13
package/dist/drivers/helpers.js
CHANGED
|
@@ -1,206 +1 @@
|
|
|
1
|
-
import { log }
|
|
2
|
-
import { getTableName } from "@stacksjs/orm";
|
|
3
|
-
import { path } from "@stacksjs/path";
|
|
4
|
-
import { fs, globSync } from "@stacksjs/storage";
|
|
5
|
-
import { plural, snakeCase } from "@stacksjs/strings";
|
|
6
|
-
import { db } from "../utils";
|
|
7
|
-
import { enumValidator, isBooleanValidator, isDatetimeValidator, isDateValidator, isFloatValidator, isNumberValidator, isStringValidator, isTimestampValidator, isUnixValidator } from "../validators";
|
|
8
|
-
export async function deleteMigrationFiles() {
|
|
9
|
-
const files = await fs.promises.readdir(path.userMigrationsPath());
|
|
10
|
-
if (files.length) {
|
|
11
|
-
for (const file of files)
|
|
12
|
-
if (file.endsWith(".ts")) {
|
|
13
|
-
const migrationPath = path.userMigrationsPath(`${file}`);
|
|
14
|
-
if (fs.existsSync(migrationPath))
|
|
15
|
-
await Bun.$`rm ${migrationPath}`;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export async function deleteFrameworkModels() {
|
|
20
|
-
const cacheDir = path.frameworkPath("cache/models");
|
|
21
|
-
if (!fs.existsSync(cacheDir))
|
|
22
|
-
return;
|
|
23
|
-
const modelFiles = await fs.promises.readdir(cacheDir);
|
|
24
|
-
if (modelFiles.length) {
|
|
25
|
-
for (const modelFile of modelFiles)
|
|
26
|
-
if (modelFile.endsWith(".ts")) {
|
|
27
|
-
const modelPath = path.frameworkPath(`cache/models/${modelFile}`);
|
|
28
|
-
if (fs.existsSync(modelPath))
|
|
29
|
-
await Bun.$`rm ${modelPath}`;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export async function getLastMigrationFields(modelName) {
|
|
34
|
-
const model = (await import(path.frameworkPath(`cache/models/${modelName}`))).default;
|
|
35
|
-
let fields = {};
|
|
36
|
-
if (typeof model.attributes === "object")
|
|
37
|
-
fields = model.attributes;
|
|
38
|
-
else
|
|
39
|
-
try {
|
|
40
|
-
fields = JSON.parse(model.attributes || "{}");
|
|
41
|
-
} catch {
|
|
42
|
-
fields = {};
|
|
43
|
-
}
|
|
44
|
-
return fields;
|
|
45
|
-
}
|
|
46
|
-
export async function modelTableName(model) {
|
|
47
|
-
if (typeof model === "string")
|
|
48
|
-
model = (await import(model)).default;
|
|
49
|
-
return model.table ?? snakeCase(plural(model?.name || ""));
|
|
50
|
-
}
|
|
51
|
-
export async function hasTableBeenMigrated(tableName) {
|
|
52
|
-
log.debug(`hasTableBeenMigrated for table: ${tableName}`);
|
|
53
|
-
return (await getExecutedMigrations()).some((migration) => migration.name.includes(tableName));
|
|
54
|
-
}
|
|
55
|
-
export async function hasMigrationBeenCreated(tableName) {
|
|
56
|
-
log.debug(`hasTableBeenMigrated for table: ${tableName}`);
|
|
57
|
-
return globSync([path.userMigrationsPath("*.ts")], { absolute: !0 }).some((path) => path.includes(`create-${tableName}`));
|
|
58
|
-
}
|
|
59
|
-
export async function getExecutedMigrations() {
|
|
60
|
-
try {
|
|
61
|
-
return await db.selectFrom("migrations").select("name").execute();
|
|
62
|
-
} catch (error) {
|
|
63
|
-
if (error?.message.includes("no such table: migrations")) {
|
|
64
|
-
console.warn("Migrations table does not exist, returning empty list.");
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
return [];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function findCharacterLength(validator) {
|
|
71
|
-
if ("getRules" in validator) {
|
|
72
|
-
const maxLengthRule = validator.getRules().find((rule) => rule.name === "max");
|
|
73
|
-
return maxLengthRule?.params?.length || maxLengthRule?.params?.max || 255;
|
|
74
|
-
}
|
|
75
|
-
return 255;
|
|
76
|
-
}
|
|
77
|
-
export function prepareTextColumnType(validator, driver = "mysql") {
|
|
78
|
-
if (driver === "sqlite")
|
|
79
|
-
return "'text'";
|
|
80
|
-
return `'varchar(${findCharacterLength(validator)})'`;
|
|
81
|
-
}
|
|
82
|
-
export function prepareDateTimeColumnType(validator, driver = "mysql") {
|
|
83
|
-
if (driver === "sqlite")
|
|
84
|
-
return "'text'";
|
|
85
|
-
const name = validator.name;
|
|
86
|
-
if (name === "unix")
|
|
87
|
-
return "'bigint'";
|
|
88
|
-
return name || "date";
|
|
89
|
-
}
|
|
90
|
-
export function compareRanges(range1, range2) {
|
|
91
|
-
return range1.min === range2.min && range1.max === range2.max;
|
|
92
|
-
}
|
|
93
|
-
export async function checkPivotMigration(dynamicPart) {
|
|
94
|
-
return (await fs.promises.readdir(path.userMigrationsPath())).some((migrationFile) => {
|
|
95
|
-
const escapedDynamicPart = dynamicPart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
96
|
-
return new RegExp(`(-${escapedDynamicPart}-)`).test(migrationFile);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
export function pluckChanges(array1, array2) {
|
|
100
|
-
const removed = array1.filter((item) => !array2.includes(item)), added = array2.filter((item) => !array1.includes(item));
|
|
101
|
-
if (removed.length === 0 && added.length === 0)
|
|
102
|
-
return null;
|
|
103
|
-
return { added, removed };
|
|
104
|
-
}
|
|
105
|
-
export function arrangeColumns(attributes) {
|
|
106
|
-
if (!attributes)
|
|
107
|
-
return [];
|
|
108
|
-
const entries = Object.entries(attributes);
|
|
109
|
-
entries.sort(([_keyA, valueA], [_keyB, valueB]) => {
|
|
110
|
-
const orderA = valueA.order ?? Number.POSITIVE_INFINITY, orderB = valueB.order ?? Number.POSITIVE_INFINITY;
|
|
111
|
-
return orderA - orderB;
|
|
112
|
-
});
|
|
113
|
-
return entries;
|
|
114
|
-
}
|
|
115
|
-
export function isArrayEqual(arr1, arr2) {
|
|
116
|
-
if (!arr1 || !arr2)
|
|
117
|
-
return !1;
|
|
118
|
-
if (arr1.length !== arr2.length)
|
|
119
|
-
return !1;
|
|
120
|
-
for (let i = 0;i < arr1.length; i++)
|
|
121
|
-
if (arr1[i] !== arr2[i])
|
|
122
|
-
return !1;
|
|
123
|
-
return !0;
|
|
124
|
-
}
|
|
125
|
-
export function findDifferingKeys(obj1, obj2) {
|
|
126
|
-
const differingKeys = [];
|
|
127
|
-
for (const key in obj1)
|
|
128
|
-
if (Object.prototype.hasOwnProperty.call(obj1, key) && Object.prototype.hasOwnProperty.call(obj2, key)) {
|
|
129
|
-
const lastCharacterLength = findCharacterLength(obj1[key].validation.rule), latestCharacterLength = findCharacterLength(obj2[key].validation.rule);
|
|
130
|
-
if (lastCharacterLength !== void 0 && latestCharacterLength !== void 0) {
|
|
131
|
-
if (lastCharacterLength !== latestCharacterLength)
|
|
132
|
-
differingKeys.push({ key, max: latestCharacterLength, min: latestCharacterLength });
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return differingKeys;
|
|
136
|
-
}
|
|
137
|
-
export async function fetchTables() {
|
|
138
|
-
const modelFiles = globSync([path.userModelsPath("*.ts"), path.storagePath("framework/defaults/app/Models/**/*.ts")], { absolute: !0 }), tables = [];
|
|
139
|
-
for (const modelPath of modelFiles) {
|
|
140
|
-
const model = (await import(modelPath)).default, tableName = getTableName(model, modelPath), upvoteTable = getUpvoteTableName(model, tableName);
|
|
141
|
-
if (upvoteTable)
|
|
142
|
-
tables.push(upvoteTable);
|
|
143
|
-
tables.push(tableName);
|
|
144
|
-
}
|
|
145
|
-
return tables;
|
|
146
|
-
}
|
|
147
|
-
export function getUpvoteTableName(model, tableName) {
|
|
148
|
-
const defaultTable = `${tableName}_likes`, traits = model.traits;
|
|
149
|
-
if (!traits?.likeable)
|
|
150
|
-
return;
|
|
151
|
-
return typeof traits.likeable === "object" ? traits.likeable.table || defaultTable : defaultTable;
|
|
152
|
-
}
|
|
153
|
-
export function getLikeableForeignKey(model, tableName) {
|
|
154
|
-
const likeable = model.traits?.likeable;
|
|
155
|
-
if (likeable && typeof likeable === "object" && !Array.isArray(likeable) && likeable.foreignKey)
|
|
156
|
-
return likeable.foreignKey;
|
|
157
|
-
return `${tableName.replace(/s$/, "")}_id`;
|
|
158
|
-
}
|
|
159
|
-
export function prepareNumberColumnType(validator, driver = "mysql") {
|
|
160
|
-
if (driver === "sqlite")
|
|
161
|
-
return "'integer'";
|
|
162
|
-
if ("getRules" in validator) {
|
|
163
|
-
const minRule = validator.getRules().find((rule) => rule.name === "min"), maxRule = validator.getRules().find((rule) => rule.name === "max"), min = minRule?.params?.min ?? -2147483648, max = maxRule?.params?.max ?? 2147483647;
|
|
164
|
-
return min >= -2147483648 && max <= 2147483647 ? "'integer'" : "'bigint'";
|
|
165
|
-
}
|
|
166
|
-
return "'integer'";
|
|
167
|
-
}
|
|
168
|
-
export function prepareEnumColumnType(validator, driver = "mysql") {
|
|
169
|
-
const allowedValues = validator.getAllowedValues();
|
|
170
|
-
if (!allowedValues)
|
|
171
|
-
throw Error("Enum rule found but no allowedValues defined");
|
|
172
|
-
const enumStructure = allowedValues.map((value) => `'${value}'`).join(", ");
|
|
173
|
-
if (driver === "postgres")
|
|
174
|
-
return "'varchar(255)'";
|
|
175
|
-
if (driver === "sqlite")
|
|
176
|
-
return "'text'";
|
|
177
|
-
return `sql\`enum(${enumStructure})\``;
|
|
178
|
-
}
|
|
179
|
-
export function mapFieldTypeToColumnType(validator, driver = "mysql") {
|
|
180
|
-
if (enumValidator(validator))
|
|
181
|
-
return prepareEnumColumnType(validator, driver);
|
|
182
|
-
if (isStringValidator(validator))
|
|
183
|
-
return prepareTextColumnType(validator, driver);
|
|
184
|
-
if (isNumberValidator(validator))
|
|
185
|
-
return prepareNumberColumnType(validator, driver);
|
|
186
|
-
if (isBooleanValidator(validator))
|
|
187
|
-
return "'boolean'";
|
|
188
|
-
if (isDateValidator(validator))
|
|
189
|
-
return "'date'";
|
|
190
|
-
if (isDatetimeValidator(validator))
|
|
191
|
-
return driver === "postgres" ? "'timestamp'" : "'datetime'";
|
|
192
|
-
if (isUnixValidator(validator))
|
|
193
|
-
return "'bigint'";
|
|
194
|
-
if (isTimestampValidator(validator))
|
|
195
|
-
return "'timestamp'";
|
|
196
|
-
if (isFloatValidator(validator))
|
|
197
|
-
return "'float4'";
|
|
198
|
-
if (["array", "object"].includes(validator.name))
|
|
199
|
-
return driver === "sqlite" ? "'text'" : "'json'";
|
|
200
|
-
if (driver === "sqlite")
|
|
201
|
-
return "'text'";
|
|
202
|
-
return driver === "mysql" ? "'varchar(255)'" : "'text'";
|
|
203
|
-
}
|
|
204
|
-
export function checkIsRequired(rule) {
|
|
205
|
-
return rule.includes(".required()");
|
|
206
|
-
}
|
|
1
|
+
import{log}from"@stacksjs/logging";import{getTableName}from"@stacksjs/orm";import{path}from"@stacksjs/path";import{fs,globSync}from"@stacksjs/storage";import{plural,snakeCase}from"@stacksjs/strings";import{db}from"../utils";import{enumValidator,isBooleanValidator,isDatetimeValidator,isDateValidator,isFloatValidator,isNumberValidator,isStringValidator,isTimestampValidator,isUnixValidator}from"../validators";export async function deleteMigrationFiles(){const files=await fs.promises.readdir(path.userMigrationsPath());if(files.length){for(const file of files)if(file.endsWith(".ts")){const migrationPath=path.userMigrationsPath(`${file}`);if(fs.existsSync(migrationPath))await Bun.$`rm ${migrationPath}`}}}export async function deleteFrameworkModels(){const cacheDir=path.frameworkPath("cache/models");if(!fs.existsSync(cacheDir))return;const modelFiles=await fs.promises.readdir(cacheDir);if(modelFiles.length){for(const modelFile of modelFiles)if(modelFile.endsWith(".ts")){const modelPath=path.frameworkPath(`cache/models/${modelFile}`);if(fs.existsSync(modelPath))await Bun.$`rm ${modelPath}`}}}export async function getLastMigrationFields(modelName){const model=(await import(path.frameworkPath(`cache/models/${modelName}`))).default;let fields={};if(typeof model.attributes==="object")fields=model.attributes;else try{fields=JSON.parse(model.attributes||"{}")}catch{fields={}}return fields}export async function modelTableName(model){if(typeof model==="string")model=(await import(model)).default;return model.table??snakeCase(plural(model?.name||""))}export async function hasTableBeenMigrated(tableName){log.debug(`hasTableBeenMigrated for table: ${tableName}`);return(await getExecutedMigrations()).some((migration)=>migration.name.includes(tableName))}export async function hasMigrationBeenCreated(tableName){log.debug(`hasTableBeenMigrated for table: ${tableName}`);return globSync([path.userMigrationsPath("*.ts")],{absolute:!0}).some((path)=>path.includes(`create-${tableName}`))}export async function getExecutedMigrations(){try{return await db.selectFrom("migrations").select("name").execute()}catch(error){if(error?.message.includes("no such table: migrations")){console.warn("Migrations table does not exist, returning empty list.");return[]}return[]}}function findCharacterLength(validator){if("getRules"in validator){const maxLengthRule=validator.getRules().find((rule)=>rule.name==="max");return maxLengthRule?.params?.length||maxLengthRule?.params?.max||255}return 255}export function prepareTextColumnType(validator,driver="mysql"){if(driver==="sqlite")return"'text'";return`'varchar(${findCharacterLength(validator)})'`}export function prepareDateTimeColumnType(validator,driver="mysql"){if(driver==="sqlite")return"'text'";const name=validator.name;if(name==="unix")return"'bigint'";return name||"date"}export function compareRanges(range1,range2){return range1.min===range2.min&&range1.max===range2.max}export async function checkPivotMigration(dynamicPart){return(await fs.promises.readdir(path.userMigrationsPath())).some((migrationFile)=>{const escapedDynamicPart=dynamicPart.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`(-${escapedDynamicPart}-)`).test(migrationFile)})}export function pluckChanges(array1,array2){const removed=array1.filter((item)=>!array2.includes(item)),added=array2.filter((item)=>!array1.includes(item));if(removed.length===0&&added.length===0)return null;return{added,removed}}export function arrangeColumns(attributes){if(!attributes)return[];const entries=Object.entries(attributes);entries.sort(([_keyA,valueA],[_keyB,valueB])=>{const orderA=valueA.order??Number.POSITIVE_INFINITY,orderB=valueB.order??Number.POSITIVE_INFINITY;return orderA-orderB});return entries}export function isArrayEqual(arr1,arr2){if(!arr1||!arr2)return!1;if(arr1.length!==arr2.length)return!1;for(let i=0;i<arr1.length;i++)if(arr1[i]!==arr2[i])return!1;return!0}export function findDifferingKeys(obj1,obj2){const differingKeys=[];for(const key in obj1)if(Object.prototype.hasOwnProperty.call(obj1,key)&&Object.prototype.hasOwnProperty.call(obj2,key)){const lastCharacterLength=findCharacterLength(obj1[key].validation.rule),latestCharacterLength=findCharacterLength(obj2[key].validation.rule);if(lastCharacterLength!==void 0&&latestCharacterLength!==void 0){if(lastCharacterLength!==latestCharacterLength)differingKeys.push({key,max:latestCharacterLength,min:latestCharacterLength})}}return differingKeys}export async function fetchTables(){const modelFiles=globSync([path.userModelsPath("*.ts"),path.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}),tables=[];for(const modelPath of modelFiles){const model=(await import(modelPath)).default,tableName=getTableName(model,modelPath),upvoteTable=getUpvoteTableName(model,tableName);if(upvoteTable)tables.push(upvoteTable);tables.push(tableName)}return tables}export function getUpvoteTableName(model,tableName){const defaultTable=`${tableName}_likes`,traits=model.traits;if(!traits?.likeable)return;return typeof traits.likeable==="object"?traits.likeable.table||defaultTable:defaultTable}export function getLikeableForeignKey(model,tableName){const likeable=model.traits?.likeable;if(likeable&&typeof likeable==="object"&&!Array.isArray(likeable)&&likeable.foreignKey)return likeable.foreignKey;return`${tableName.replace(/s$/,"")}_id`}export function prepareNumberColumnType(validator,driver="mysql"){if(driver==="sqlite")return"'integer'";if("getRules"in validator){const minRule=validator.getRules().find((rule)=>rule.name==="min"),maxRule=validator.getRules().find((rule)=>rule.name==="max"),min=minRule?.params?.min??-2147483648,max=maxRule?.params?.max??2147483647;return min>=-2147483648&&max<=2147483647?"'integer'":"'bigint'"}return"'integer'"}export function prepareEnumColumnType(validator,driver="mysql"){const allowedValues=validator.getAllowedValues();if(!allowedValues)throw Error("Enum rule found but no allowedValues defined");const enumStructure=allowedValues.map((value)=>`'${value}'`).join(", ");if(driver==="postgres")return"'varchar(255)'";if(driver==="sqlite")return"'text'";return`sql\`enum(${enumStructure})\``}export function mapFieldTypeToColumnType(validator,driver="mysql"){if(enumValidator(validator))return prepareEnumColumnType(validator,driver);if(isStringValidator(validator))return prepareTextColumnType(validator,driver);if(isNumberValidator(validator))return prepareNumberColumnType(validator,driver);if(isBooleanValidator(validator))return"'boolean'";if(isDateValidator(validator))return"'date'";if(isDatetimeValidator(validator))return driver==="postgres"?"'timestamp'":"'datetime'";if(isUnixValidator(validator))return"'bigint'";if(isTimestampValidator(validator))return"'timestamp'";if(isFloatValidator(validator))return"'float4'";if(["array","object"].includes(validator.name))return driver==="sqlite"?"'text'":"'json'";if(driver==="sqlite")return"'text'";return driver==="mysql"?"'varchar(255)'":"'text'"}export function checkIsRequired(rule){return rule.includes(".required()")}
|
package/dist/drivers/index.js
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./mysql";
|
|
3
|
-
export * from "./postgres";
|
|
4
|
-
export * from "./sqlite";
|
|
5
|
-
export { generateIndexCreationSQL } from "./mysql";
|
|
6
|
-
export { generateIndexCreationSQL as generatePostgresIndexCreationSQL } from "./postgres";
|
|
7
|
-
export { generateIndexCreationSQL as generateSqliteIndexCreationSQL } from "./sqlite";
|
|
8
|
-
export * from "./defaults";
|
|
9
|
-
export * from "./dynamodb";
|
|
1
|
+
export*from"./helpers";export*from"./mysql";export*from"./postgres";export*from"./sqlite";export{generateIndexCreationSQL}from"./mysql";export{generateIndexCreationSQL as generatePostgresIndexCreationSQL}from"./postgres";export{generateIndexCreationSQL as generateSqliteIndexCreationSQL}from"./sqlite";export*from"./defaults";export*from"./dynamodb";
|
package/dist/drivers/mysql.js
CHANGED
|
@@ -1,309 +1,68 @@
|
|
|
1
|
-
import { log } from
|
|
2
|
-
|
|
3
|
-
return `\x1B[3m${str}\x1B[23m`;
|
|
4
|
-
}
|
|
5
|
-
import { db } from "../utils";
|
|
6
|
-
import { ok } from "@stacksjs/error-handling";
|
|
7
|
-
import { fetchOtherModelRelations, getModelName, getPivotTables, getTableName } from "@stacksjs/orm";
|
|
8
|
-
import { path } from "@stacksjs/path";
|
|
9
|
-
import { fs, globSync } from "@stacksjs/storage";
|
|
10
|
-
import { snakeCase } from "@stacksjs/strings";
|
|
11
|
-
import {
|
|
12
|
-
arrangeColumns,
|
|
13
|
-
checkPivotMigration,
|
|
14
|
-
deleteFrameworkModels,
|
|
15
|
-
deleteMigrationFiles,
|
|
16
|
-
fetchTables,
|
|
17
|
-
findDifferingKeys,
|
|
18
|
-
getLastMigrationFields,
|
|
19
|
-
getLikeableForeignKey,
|
|
20
|
-
getUpvoteTableName,
|
|
21
|
-
hasTableBeenMigrated,
|
|
22
|
-
isArrayEqual,
|
|
23
|
-
mapFieldTypeToColumnType,
|
|
24
|
-
pluckChanges
|
|
25
|
-
} from "./helpers";
|
|
26
|
-
import { dropCommonTables } from "./defaults/traits";
|
|
27
|
-
export async function resetMysqlDatabase() {
|
|
28
|
-
await dropMysqlTables();
|
|
29
|
-
await deleteFrameworkModels();
|
|
30
|
-
await deleteMigrationFiles();
|
|
31
|
-
return ok("All tables dropped successfully!");
|
|
32
|
-
}
|
|
33
|
-
export async function dropMysqlTables() {
|
|
34
|
-
const modelFiles = globSync([path.userModelsPath("*.ts"), path.storagePath("framework/defaults/app/Models/**/*.ts")], { absolute: !0 }), tables = await fetchTables();
|
|
35
|
-
for (const table of tables) {
|
|
36
|
-
if (!/^[a-z_][\w]*$/i.test(table))
|
|
37
|
-
throw Error(`[mysql] Refusing to drop table with unsafe name: ${table}`);
|
|
38
|
-
await db.unsafe(`DROP TABLE IF EXISTS \`${table}\``).execute();
|
|
39
|
-
}
|
|
40
|
-
await dropCommonTables();
|
|
41
|
-
for (const userModel of modelFiles) {
|
|
42
|
-
const userModelPath = (await import(userModel)).default, pivotTables = await getPivotTables(userModelPath, userModel);
|
|
43
|
-
for (const pivotTable of pivotTables) {
|
|
44
|
-
if (!/^[a-z_][\w]*$/i.test(pivotTable.table))
|
|
45
|
-
throw Error(`[mysql] Refusing to drop pivot table with unsafe name: ${pivotTable.table}`);
|
|
46
|
-
await db.unsafe(`DROP TABLE IF EXISTS \`${pivotTable.table}\``).execute();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export async function generateMysqlMigration(modelPath) {
|
|
51
|
-
const model = (await import(modelPath)).default, fileName = path.basename(modelPath), tableName = getTableName(model, modelPath), fieldsString = JSON.stringify(model.attributes, null, 2), copiedModelPath = path.frameworkPath(`cache/models/${fileName}`);
|
|
52
|
-
let haveFieldsChanged = !1;
|
|
53
|
-
if (fs.existsSync(copiedModelPath)) {
|
|
54
|
-
log.info(`Fields have already been generated for ${tableName}`);
|
|
55
|
-
const previousFields = await getLastMigrationFields(fileName);
|
|
56
|
-
if (JSON.stringify(previousFields, null, 2) === fieldsString) {
|
|
57
|
-
log.debug(`Fields have not changed for ${tableName}`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
haveFieldsChanged = !0;
|
|
61
|
-
log.debug(`Fields have changed for ${tableName}`);
|
|
62
|
-
} else
|
|
63
|
-
log.debug(`Fields have not been generated for ${tableName}`);
|
|
64
|
-
await Bun.$`mkdir -p ${path.frameworkPath("cache/models")} && cp ${modelPath} ${copiedModelPath}`;
|
|
65
|
-
const hasBeenMigrated = await hasTableBeenMigrated(tableName);
|
|
66
|
-
log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`);
|
|
67
|
-
if (haveFieldsChanged)
|
|
68
|
-
await createAlterTableMigration(modelPath);
|
|
69
|
-
else
|
|
70
|
-
await createTableMigration(modelPath);
|
|
71
|
-
}
|
|
72
|
-
async function createTableMigration(modelPath) {
|
|
73
|
-
log.debug("createTableMigration modelPath:", modelPath);
|
|
74
|
-
const model = (await import(modelPath)).default, tableName = getTableName(model, modelPath), twoFactorEnabled = model.traits?.useAuth && typeof model.traits.useAuth !== "boolean" ? model.traits.useAuth.useTwoFactor : !1;
|
|
75
|
-
await createPivotTableMigration(model, modelPath);
|
|
76
|
-
const useTimestamps = model?.traits?.useTimestamps ?? model?.traits?.timestampable ?? !0, useSocials = model?.traits?.useSocials && Array.isArray(model.traits.useSocials) && model.traits.useSocials.length > 0, useLikeable = Array.isArray(model?.traits?.likeable) ? model.traits.likeable.length > 0 : Boolean(model?.traits?.likeable), useSoftDeletes = model?.traits?.useSoftDeletes ?? model?.traits?.softDeletable ?? !1, usePasskey = (typeof model.traits?.useAuth === "object" && model.traits.useAuth.usePasskey) ?? !1, useBillable = model.traits?.billable || !1, useUuid = model.traits?.useUuid || !1;
|
|
77
|
-
if (useBillable && tableName === "users")
|
|
78
|
-
await createTableMigration(path.frameworkPath("defaults/app/Models/Subscription.ts"));
|
|
79
|
-
let migrationContent = `import type { Database } from '@stacksjs/database'
|
|
80
|
-
`;
|
|
81
|
-
migrationContent += `import { sql } from '@stacksjs/database'
|
|
1
|
+
import{log}from"@stacksjs/logging";function italic(str){return`\x1B[3m${str}\x1B[23m`}import{db}from"../utils";import{ok}from"@stacksjs/error-handling";import{fetchOtherModelRelations,getModelName,getPivotTables,getTableName}from"@stacksjs/orm";import{path}from"@stacksjs/path";import{fs,globSync}from"@stacksjs/storage";import{snakeCase}from"@stacksjs/strings";import{arrangeColumns,checkPivotMigration,deleteFrameworkModels,deleteMigrationFiles,fetchTables,findDifferingKeys,getLastMigrationFields,getLikeableForeignKey,getUpvoteTableName,hasTableBeenMigrated,isArrayEqual,mapFieldTypeToColumnType,pluckChanges}from"./helpers";import{dropCommonTables}from"./defaults/traits";export async function resetMysqlDatabase(){await dropMysqlTables();await deleteFrameworkModels();await deleteMigrationFiles();return ok("All tables dropped successfully!")}export async function dropMysqlTables(){const modelFiles=globSync([path.userModelsPath("*.ts"),path.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}),tables=await fetchTables();for(const table of tables){if(!/^[a-z_][\w]*$/i.test(table))throw Error(`[mysql] Refusing to drop table with unsafe name: ${table}`);await db.unsafe(`DROP TABLE IF EXISTS \`${table}\``).execute()}await dropCommonTables();for(const userModel of modelFiles){const userModelPath=(await import(userModel)).default,pivotTables=await getPivotTables(userModelPath,userModel);for(const pivotTable of pivotTables){if(!/^[a-z_][\w]*$/i.test(pivotTable.table))throw Error(`[mysql] Refusing to drop pivot table with unsafe name: ${pivotTable.table}`);await db.unsafe(`DROP TABLE IF EXISTS \`${pivotTable.table}\``).execute()}}}export async function generateMysqlMigration(modelPath){const model=(await import(modelPath)).default,fileName=path.basename(modelPath),tableName=getTableName(model,modelPath),fieldsString=JSON.stringify(model.attributes,null,2),copiedModelPath=path.frameworkPath(`cache/models/${fileName}`);let haveFieldsChanged=!1;if(fs.existsSync(copiedModelPath)){log.info(`Fields have already been generated for ${tableName}`);const previousFields=await getLastMigrationFields(fileName);if(JSON.stringify(previousFields,null,2)===fieldsString){log.debug(`Fields have not changed for ${tableName}`);return}haveFieldsChanged=!0;log.debug(`Fields have changed for ${tableName}`)}else log.debug(`Fields have not been generated for ${tableName}`);await Bun.$`mkdir -p ${path.frameworkPath("cache/models")} && cp ${modelPath} ${copiedModelPath}`;const hasBeenMigrated=await hasTableBeenMigrated(tableName);log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`);if(haveFieldsChanged)await createAlterTableMigration(modelPath);else await createTableMigration(modelPath)}async function createTableMigration(modelPath){log.debug("createTableMigration modelPath:",modelPath);const model=(await import(modelPath)).default,tableName=getTableName(model,modelPath),twoFactorEnabled=model.traits?.useAuth&&typeof model.traits.useAuth!=="boolean"?model.traits.useAuth.useTwoFactor:!1;await createPivotTableMigration(model,modelPath);const useTimestamps=model?.traits?.useTimestamps??model?.traits?.timestampable??!0,useSocials=model?.traits?.useSocials&&Array.isArray(model.traits.useSocials)&&model.traits.useSocials.length>0,useLikeable=Array.isArray(model?.traits?.likeable)?model.traits.likeable.length>0:Boolean(model?.traits?.likeable),useSoftDeletes=model?.traits?.useSoftDeletes??model?.traits?.softDeletable??!1,usePasskey=(typeof model.traits?.useAuth==="object"&&model.traits.useAuth.usePasskey)??!1,useBillable=model.traits?.billable||!1,useUuid=model.traits?.useUuid||!1;if(useBillable&&tableName==="users")await createTableMigration(path.frameworkPath("defaults/app/Models/Subscription.ts"));let migrationContent=`import type { Database } from '@stacksjs/database'
|
|
2
|
+
`;migrationContent+=`import { sql } from '@stacksjs/database'
|
|
82
3
|
|
|
83
|
-
`;
|
|
84
|
-
|
|
85
|
-
`;
|
|
86
|
-
|
|
87
|
-
`;
|
|
88
|
-
|
|
89
|
-
`;
|
|
90
|
-
|
|
91
|
-
`;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`;
|
|
103
|
-
if (socials.includes("apple"))
|
|
104
|
-
migrationContent += ` .addColumn('apple_id', 'varchar(255)')
|
|
105
|
-
`;
|
|
106
|
-
if (socials.includes("twitter"))
|
|
107
|
-
migrationContent += ` .addColumn('twitter_id', 'varchar(255)')
|
|
108
|
-
`;
|
|
109
|
-
if (socials.includes("facebook"))
|
|
110
|
-
migrationContent += ` .addColumn('facebook_id', 'varchar(255)')
|
|
111
|
-
`;
|
|
112
|
-
}
|
|
113
|
-
for (const [fieldName, options] of arrangeColumns(model.attributes)) {
|
|
114
|
-
const fieldOptions = options, fieldNameFormatted = snakeCase(fieldName), columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule);
|
|
115
|
-
migrationContent += ` .addColumn('${fieldNameFormatted}', ${columnType}`;
|
|
116
|
-
const isRequired = "isRequired" in (fieldOptions.validation?.rule ?? {}) ? (fieldOptions.validation?.rule).isRequired : !1;
|
|
117
|
-
if (isRequired || fieldOptions.unique || fieldOptions.default !== void 0) {
|
|
118
|
-
migrationContent += ", col => col";
|
|
119
|
-
if (isRequired)
|
|
120
|
-
migrationContent += ".notNull()";
|
|
121
|
-
if (fieldOptions.unique)
|
|
122
|
-
migrationContent += ".unique()";
|
|
123
|
-
if (fieldOptions.default !== void 0)
|
|
124
|
-
if (typeof fieldOptions.default === "string")
|
|
125
|
-
migrationContent += `.defaultTo('${fieldOptions.default.replace(/'/g, "\\'")}')`;
|
|
126
|
-
else if (fieldOptions.default === null)
|
|
127
|
-
migrationContent += ".defaultTo(null)";
|
|
128
|
-
else
|
|
129
|
-
migrationContent += `.defaultTo(${fieldOptions.default})`;
|
|
130
|
-
migrationContent += "";
|
|
131
|
-
}
|
|
132
|
-
migrationContent += `)
|
|
133
|
-
`;
|
|
134
|
-
}
|
|
135
|
-
if (twoFactorEnabled !== !1 && twoFactorEnabled)
|
|
136
|
-
migrationContent += ` .addColumn('two_factor_secret', 'varchar(255)')
|
|
137
|
-
`;
|
|
138
|
-
if (useBillable)
|
|
139
|
-
migrationContent += ` .addColumn('stripe_id', 'varchar(255)')
|
|
140
|
-
`;
|
|
141
|
-
if (usePasskey)
|
|
142
|
-
migrationContent += ` .addColumn('public_passkey', 'varchar(255)')
|
|
143
|
-
`;
|
|
144
|
-
if (useTimestamps) {
|
|
145
|
-
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
146
|
-
`;
|
|
147
|
-
migrationContent += ` .addColumn('updated_at', 'timestamp')
|
|
148
|
-
`;
|
|
149
|
-
}
|
|
150
|
-
if (useSoftDeletes)
|
|
151
|
-
migrationContent += ` .addColumn('deleted_at', 'timestamp')
|
|
152
|
-
`;
|
|
153
|
-
migrationContent += ` .execute()
|
|
154
|
-
`;
|
|
155
|
-
migrationContent += generatePrimaryKeyIndexSQL(tableName);
|
|
156
|
-
if (useLikeable) {
|
|
157
|
-
const upvoteTable = getUpvoteTableName(model, tableName);
|
|
158
|
-
if (upvoteTable) {
|
|
159
|
-
const foreignKey = getLikeableForeignKey(model, tableName);
|
|
160
|
-
migrationContent += `
|
|
4
|
+
`;migrationContent+=`export async function up(db: Database<any>) {
|
|
5
|
+
`;migrationContent+=` await (db as any).schema
|
|
6
|
+
`;migrationContent+=` .createTable('${tableName}')
|
|
7
|
+
`;migrationContent+=` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
8
|
+
`;if(useUuid)migrationContent+=` .addColumn('uuid', 'varchar(255)')
|
|
9
|
+
`;if(useSocials){const socials=model.traits?.useSocials||[];if(socials.includes("google"))migrationContent+=` .addColumn('google_id', 'varchar(255)')
|
|
10
|
+
`;if(socials.includes("github"))migrationContent+=` .addColumn('github_id', 'varchar(255)')
|
|
11
|
+
`;if(socials.includes("apple"))migrationContent+=` .addColumn('apple_id', 'varchar(255)')
|
|
12
|
+
`;if(socials.includes("twitter"))migrationContent+=` .addColumn('twitter_id', 'varchar(255)')
|
|
13
|
+
`;if(socials.includes("facebook"))migrationContent+=` .addColumn('facebook_id', 'varchar(255)')
|
|
14
|
+
`}for(const[fieldName,options]of arrangeColumns(model.attributes)){const fieldOptions=options,fieldNameFormatted=snakeCase(fieldName),columnType=mapFieldTypeToColumnType(fieldOptions.validation?.rule);migrationContent+=` .addColumn('${fieldNameFormatted}', ${columnType}`;const isRequired="isRequired"in(fieldOptions.validation?.rule??{})?(fieldOptions.validation?.rule).isRequired:!1;if(isRequired||fieldOptions.unique||fieldOptions.default!==void 0){migrationContent+=", col => col";if(isRequired)migrationContent+=".notNull()";if(fieldOptions.unique)migrationContent+=".unique()";if(fieldOptions.default!==void 0)if(typeof fieldOptions.default==="string")migrationContent+=`.defaultTo('${fieldOptions.default.replace(/'/g,"\\'")}')`;else if(fieldOptions.default===null)migrationContent+=".defaultTo(null)";else migrationContent+=`.defaultTo(${fieldOptions.default})`;migrationContent+=""}migrationContent+=`)
|
|
15
|
+
`}if(twoFactorEnabled!==!1&&twoFactorEnabled)migrationContent+=` .addColumn('two_factor_secret', 'varchar(255)')
|
|
16
|
+
`;if(useBillable)migrationContent+=` .addColumn('stripe_id', 'varchar(255)')
|
|
17
|
+
`;if(usePasskey)migrationContent+=` .addColumn('public_passkey', 'varchar(255)')
|
|
18
|
+
`;if(useTimestamps){migrationContent+=` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
19
|
+
`;migrationContent+=` .addColumn('updated_at', 'timestamp')
|
|
20
|
+
`}if(useSoftDeletes)migrationContent+=` .addColumn('deleted_at', 'timestamp')
|
|
21
|
+
`;migrationContent+=` .execute()
|
|
22
|
+
`;migrationContent+=generatePrimaryKeyIndexSQL(tableName);if(useLikeable){const upvoteTable=getUpvoteTableName(model,tableName);if(upvoteTable){const foreignKey=getLikeableForeignKey(model,tableName);migrationContent+=`
|
|
161
23
|
// Create upvote table
|
|
162
|
-
`;
|
|
163
|
-
|
|
164
|
-
`;
|
|
165
|
-
|
|
166
|
-
`;
|
|
167
|
-
|
|
168
|
-
`;
|
|
169
|
-
|
|
170
|
-
`;
|
|
171
|
-
migrationContent += ` .addColumn('user_id', 'integer', col => col.notNull())
|
|
172
|
-
`;
|
|
173
|
-
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
174
|
-
`;
|
|
175
|
-
migrationContent += ` .addColumn('updated_at', 'timestamp')
|
|
176
|
-
`;
|
|
177
|
-
migrationContent += ` .execute()
|
|
24
|
+
`;migrationContent+=` await (db as any).schema
|
|
25
|
+
`;migrationContent+=` .createTable('${upvoteTable}')
|
|
26
|
+
`;migrationContent+=` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
27
|
+
`;migrationContent+=` .addColumn('${foreignKey}', 'integer', col => col.notNull())
|
|
28
|
+
`;migrationContent+=` .addColumn('user_id', 'integer', col => col.notNull())
|
|
29
|
+
`;migrationContent+=` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
30
|
+
`;migrationContent+=` .addColumn('updated_at', 'timestamp')
|
|
31
|
+
`;migrationContent+=` .execute()
|
|
178
32
|
|
|
179
|
-
`;
|
|
180
|
-
|
|
181
|
-
`;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
`;
|
|
186
|
-
migrationContent += ` await (db as any).schema.createIndex('${upvoteTable}_id_index').on('${upvoteTable}').column('id').execute()
|
|
187
|
-
`;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
migrationContent += `}
|
|
191
|
-
`;
|
|
192
|
-
const migrationFileName = `${new Date().getTime().toString()}-create-${tableName}-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
|
|
193
|
-
log.debug(migrationFilePath);
|
|
194
|
-
await Bun.write(migrationFilePath, migrationContent);
|
|
195
|
-
log.success(`Created migration: ${italic(migrationFileName)}`);
|
|
196
|
-
}
|
|
197
|
-
async function createPivotTableMigration(model, modelPath) {
|
|
198
|
-
const pivotTables = await getPivotTables(model, modelPath), processedPivotTables = new Set;
|
|
199
|
-
if (!pivotTables.length)
|
|
200
|
-
return;
|
|
201
|
-
for (const pivotTable of pivotTables) {
|
|
202
|
-
if (processedPivotTables.has(pivotTable.table))
|
|
203
|
-
continue;
|
|
204
|
-
if (await checkPivotMigration(pivotTable.table)) {
|
|
205
|
-
processedPivotTables.add(pivotTable.table);
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
let migrationContent = `import type { Database } from '@stacksjs/database'
|
|
209
|
-
`;
|
|
210
|
-
migrationContent += `import { sql } from '@stacksjs/database'
|
|
33
|
+
`;migrationContent+=` // Add indexes for upvote table
|
|
34
|
+
`;migrationContent+=` await (db as any).schema.createIndex('${upvoteTable}_${foreignKey}_index').on('${upvoteTable}').column('${foreignKey}').execute()
|
|
35
|
+
`;migrationContent+=` await (db as any).schema.createIndex('${upvoteTable}_user_${foreignKey}_unique').on('${upvoteTable}').columns(['user_id', '${foreignKey}']).unique().execute()
|
|
36
|
+
`;migrationContent+=` await (db as any).schema.createIndex('${upvoteTable}_id_index').on('${upvoteTable}').column('id').execute()
|
|
37
|
+
`}}migrationContent+=`}
|
|
38
|
+
`;const migrationFileName=`${new Date().getTime().toString()}-create-${tableName}-table.ts`,migrationFilePath=path.userMigrationsPath(migrationFileName);log.debug(migrationFilePath);await Bun.write(migrationFilePath,migrationContent);log.success(`Created migration: ${italic(migrationFileName)}`)}async function createPivotTableMigration(model,modelPath){const pivotTables=await getPivotTables(model,modelPath),processedPivotTables=new Set;if(!pivotTables.length)return;for(const pivotTable of pivotTables){if(processedPivotTables.has(pivotTable.table))continue;if(await checkPivotMigration(pivotTable.table)){processedPivotTables.add(pivotTable.table);continue}let migrationContent=`import type { Database } from '@stacksjs/database'
|
|
39
|
+
`;migrationContent+=`import { sql } from '@stacksjs/database'
|
|
211
40
|
|
|
212
|
-
`;
|
|
213
|
-
|
|
214
|
-
`;
|
|
215
|
-
|
|
216
|
-
`;
|
|
217
|
-
|
|
218
|
-
`;
|
|
219
|
-
|
|
220
|
-
`;
|
|
221
|
-
|
|
222
|
-
`;
|
|
223
|
-
migrationContent += ` .addColumn('${pivotTable.secondForeignKey}', 'integer')
|
|
224
|
-
`;
|
|
225
|
-
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
226
|
-
`;
|
|
227
|
-
migrationContent += ` .execute()
|
|
228
|
-
`;
|
|
229
|
-
migrationContent += ` }
|
|
230
|
-
`;
|
|
231
|
-
const migrationFileName = `${new Date().getTime().toString()}-create-${pivotTable.table}-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
|
|
232
|
-
await Bun.write(migrationFilePath, migrationContent);
|
|
233
|
-
processedPivotTables.add(pivotTable.table);
|
|
234
|
-
log.success(`Created pivot migration: ${migrationFileName}`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
export async function createAlterTableMigration(modelPath) {
|
|
238
|
-
const model = (await import(modelPath)).default, modelName = getModelName(model, modelPath), tableName = getTableName(model, modelPath);
|
|
239
|
-
let hasChanged = !1;
|
|
240
|
-
const lastFields = await getLastMigrationFields(modelName) ?? {}, currentFields = model.attributes, changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields)), fieldsToAdd = changes?.added || [], fieldsToRemove = changes?.removed || [];
|
|
241
|
-
let migrationContent = `import type { Database } from '@stacksjs/database'
|
|
242
|
-
`;
|
|
243
|
-
migrationContent += `import { sql } from '@stacksjs/database'
|
|
41
|
+
`;migrationContent+=`export async function up(db: Database<any>) {
|
|
42
|
+
`;migrationContent+=` await (db as any).schema
|
|
43
|
+
`;migrationContent+=` .createTable('${pivotTable.table}')
|
|
44
|
+
`;migrationContent+=` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
45
|
+
`;migrationContent+=` .addColumn('${pivotTable.firstForeignKey}', 'integer')
|
|
46
|
+
`;migrationContent+=` .addColumn('${pivotTable.secondForeignKey}', 'integer')
|
|
47
|
+
`;migrationContent+=` .addColumn('created_at', 'timestamp', col => col.defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
48
|
+
`;migrationContent+=` .execute()
|
|
49
|
+
`;migrationContent+=` }
|
|
50
|
+
`;const migrationFileName=`${new Date().getTime().toString()}-create-${pivotTable.table}-table.ts`,migrationFilePath=path.userMigrationsPath(migrationFileName);await Bun.write(migrationFilePath,migrationContent);processedPivotTables.add(pivotTable.table);log.success(`Created pivot migration: ${migrationFileName}`)}}export async function createAlterTableMigration(modelPath){const model=(await import(modelPath)).default,modelName=getModelName(model,modelPath),tableName=getTableName(model,modelPath);let hasChanged=!1;const lastFields=await getLastMigrationFields(modelName)??{},currentFields=model.attributes,changes=pluckChanges(Object.keys(lastFields),Object.keys(currentFields)),fieldsToAdd=changes?.added||[],fieldsToRemove=changes?.removed||[];let migrationContent=`import type { Database } from '@stacksjs/database'
|
|
51
|
+
`;migrationContent+=`import { sql } from '@stacksjs/database'
|
|
244
52
|
|
|
245
|
-
`;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
for (const fieldValidation of fieldValidations) {
|
|
255
|
-
hasChanged = !0;
|
|
256
|
-
const fieldNameFormatted = snakeCase(fieldValidation.key);
|
|
257
|
-
migrationContent += ` .modifyColumn('${fieldNameFormatted}', 'varchar(${fieldValidation.max})')
|
|
258
|
-
`;
|
|
259
|
-
}
|
|
260
|
-
const lastFieldOrder = Object.values(lastFields).map((attr) => attr.order), currentFieldOrder = Object.values(currentFields).map((attr) => attr.order);
|
|
261
|
-
if (!isArrayEqual(lastFieldOrder, currentFieldOrder)) {
|
|
262
|
-
hasChanged = !0;
|
|
263
|
-
migrationContent += reArrangeColumns(model.attributes, tableName);
|
|
264
|
-
}
|
|
265
|
-
if (hasChanged) {
|
|
266
|
-
migrationContent += ` .execute()
|
|
267
|
-
`;
|
|
268
|
-
migrationContent += `}
|
|
269
|
-
`;
|
|
270
|
-
const migrationFileName = `${new Date().getTime().toString()}-alter-${tableName}-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
|
|
271
|
-
await Bun.write(migrationFilePath, migrationContent);
|
|
272
|
-
log.success(`Created alter migration: ${italic(migrationFileName)}`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
export function generateIndexCreationSQL(tableName, index) {
|
|
276
|
-
if (index.unique || index.where) {
|
|
277
|
-
const unique = index.unique ? "UNIQUE " : "", cols = index.columns.map((col) => snakeCase(col)).join(", "), whereClause = index.where ? ` WHERE ${index.where}` : "";
|
|
278
|
-
return ` await db.unsafe(\`CREATE ${unique}INDEX IF NOT EXISTS \\\`${index.name}\\\` ON \\\`${tableName}\\\` (${cols})${whereClause}\`).execute()
|
|
279
|
-
`;
|
|
280
|
-
}
|
|
281
|
-
const columnsStr = index.columns.map((col) => `'${snakeCase(col)}'`).join(", ");
|
|
282
|
-
return ` await (db as any).schema.createIndex('${index.name}').on('${tableName}').columns([${columnsStr}]).execute()
|
|
283
|
-
`;
|
|
284
|
-
}
|
|
285
|
-
function generatePrimaryKeyIndexSQL(tableName) {
|
|
286
|
-
return ` await (db as any).schema.createIndex('${tableName}_id_index').on('${tableName}').column('id').execute()
|
|
287
|
-
`;
|
|
288
|
-
}
|
|
289
|
-
function generateForeignKeyIndexSQL(tableName, foreignKey) {
|
|
290
|
-
return ` await (db as any).schema.createIndex('${tableName}_${foreignKey}_index').on('${tableName}').column('${foreignKey}').execute()
|
|
53
|
+
`;migrationContent+=`export async function up(db: Database<any>) {
|
|
54
|
+
`;if(fieldsToAdd.length||fieldsToRemove.length){hasChanged=!0;migrationContent+=` await (db as any).schema.alterTable('${tableName}')
|
|
55
|
+
`}const fieldValidations=findDifferingKeys(lastFields,currentFields);for(const fieldValidation of fieldValidations){hasChanged=!0;const fieldNameFormatted=snakeCase(fieldValidation.key);migrationContent+=` .modifyColumn('${fieldNameFormatted}', 'varchar(${fieldValidation.max})')
|
|
56
|
+
`}const lastFieldOrder=Object.values(lastFields).map((attr)=>attr.order),currentFieldOrder=Object.values(currentFields).map((attr)=>attr.order);if(!isArrayEqual(lastFieldOrder,currentFieldOrder)){hasChanged=!0;migrationContent+=reArrangeColumns(model.attributes,tableName)}if(hasChanged){migrationContent+=` .execute()
|
|
57
|
+
`;migrationContent+=`}
|
|
58
|
+
`;const migrationFileName=`${new Date().getTime().toString()}-alter-${tableName}-table.ts`,migrationFilePath=path.userMigrationsPath(migrationFileName);await Bun.write(migrationFilePath,migrationContent);log.success(`Created alter migration: ${italic(migrationFileName)}`)}}export function generateIndexCreationSQL(tableName,index){if(index.unique||index.where){const unique=index.unique?"UNIQUE ":"",cols=index.columns.map((col)=>snakeCase(col)).join(", "),whereClause=index.where?` WHERE ${index.where}`:"";return` await db.unsafe(\`CREATE ${unique}INDEX IF NOT EXISTS \\\`${index.name}\\\` ON \\\`${tableName}\\\` (${cols})${whereClause}\`).execute()
|
|
59
|
+
`}const columnsStr=index.columns.map((col)=>`'${snakeCase(col)}'`).join(", ");return` await (db as any).schema.createIndex('${index.name}').on('${tableName}').columns([${columnsStr}]).execute()
|
|
60
|
+
`}function generatePrimaryKeyIndexSQL(tableName){return` await (db as any).schema.createIndex('${tableName}_id_index').on('${tableName}').column('id').execute()
|
|
61
|
+
`}function generateForeignKeyIndexSQL(tableName,foreignKey){return` await (db as any).schema.createIndex('${tableName}_${foreignKey}_index').on('${tableName}').column('${foreignKey}').execute()
|
|
291
62
|
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
function reArrangeColumns(attributes, tableName) {
|
|
295
|
-
const fields = arrangeColumns(attributes);
|
|
296
|
-
let migrationContent = "", previousField = "";
|
|
297
|
-
for (const [fieldName] of fields) {
|
|
298
|
-
const fieldNameFormatted = snakeCase(fieldName);
|
|
299
|
-
if (previousField)
|
|
300
|
-
migrationContent += `await sql\`
|
|
63
|
+
`}function reArrangeColumns(attributes,tableName){const fields=arrangeColumns(attributes);let migrationContent="",previousField="";for(const[fieldName]of fields){const fieldNameFormatted=snakeCase(fieldName);if(previousField)migrationContent+=`await sql\`
|
|
301
64
|
ALTER TABLE ${tableName}
|
|
302
65
|
MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
|
|
303
66
|
\`.execute(db)
|
|
304
67
|
|
|
305
|
-
`;
|
|
306
|
-
previousField = fieldNameFormatted;
|
|
307
|
-
}
|
|
308
|
-
return migrationContent;
|
|
309
|
-
}
|
|
68
|
+
`;previousField=fieldNameFormatted}return migrationContent}
|