@stacksjs/database 0.70.88 → 0.70.90

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.
Files changed (83) hide show
  1. package/dist/auth-tables.d.ts +60 -0
  2. package/dist/auth-tables.js +220 -0
  3. package/dist/class-seeder.d.ts +65 -0
  4. package/dist/class-seeder.js +116 -0
  5. package/dist/column.d.ts +17 -0
  6. package/dist/column.js +26 -0
  7. package/dist/custom/audits.d.ts +16 -0
  8. package/dist/custom/audits.js +57 -0
  9. package/dist/custom/errors.d.ts +1 -0
  10. package/dist/custom/errors.js +48 -0
  11. package/dist/custom/index.d.ts +3 -0
  12. package/dist/custom/index.js +3 -0
  13. package/dist/custom/jobs.d.ts +3 -0
  14. package/dist/custom/jobs.js +449 -0
  15. package/dist/database.d.ts +89 -0
  16. package/dist/database.js +178 -0
  17. package/dist/defaults.d.ts +48 -0
  18. package/dist/defaults.js +48 -0
  19. package/dist/driver-config.d.ts +149 -0
  20. package/dist/driver-config.js +144 -0
  21. package/dist/drivers/defaults/index.d.ts +2 -0
  22. package/dist/drivers/defaults/index.js +2 -0
  23. package/dist/drivers/defaults/passwords.d.ts +4 -0
  24. package/dist/drivers/defaults/passwords.js +106 -0
  25. package/dist/drivers/defaults/traits.d.ts +33 -0
  26. package/dist/drivers/defaults/traits.js +1125 -0
  27. package/dist/drivers/dynamodb.d.ts +200 -0
  28. package/dist/drivers/dynamodb.js +607 -0
  29. package/dist/drivers/helpers.d.ts +35 -0
  30. package/dist/drivers/helpers.js +206 -0
  31. package/dist/drivers/index.d.ts +16 -0
  32. package/dist/drivers/index.js +9 -0
  33. package/dist/drivers/mysql.d.ts +7 -0
  34. package/dist/drivers/mysql.js +322 -0
  35. package/dist/drivers/postgres.d.ts +7 -0
  36. package/dist/drivers/postgres.js +411 -0
  37. package/dist/drivers/sqlite.d.ts +20 -0
  38. package/dist/drivers/sqlite.js +397 -0
  39. package/dist/factory.d.ts +41 -0
  40. package/dist/factory.js +51 -0
  41. package/dist/fk-audit.d.ts +101 -0
  42. package/dist/fk-audit.js +181 -0
  43. package/dist/index.d.ts +149 -0
  44. package/dist/index.js +55 -0
  45. package/dist/migration-lock.d.ts +23 -0
  46. package/dist/migration-lock.js +143 -0
  47. package/dist/migrations.d.ts +76 -0
  48. package/dist/migrations.js +528 -0
  49. package/dist/notification-tables.d.ts +20 -0
  50. package/dist/notification-tables.js +54 -0
  51. package/dist/query-logger.d.ts +26 -0
  52. package/dist/query-logger.js +213 -0
  53. package/dist/query-parser.d.ts +4 -0
  54. package/dist/query-parser.js +93 -0
  55. package/dist/rbac-tables.d.ts +17 -0
  56. package/dist/rbac-tables.js +84 -0
  57. package/dist/safe-migrations.d.ts +72 -0
  58. package/dist/safe-migrations.js +59 -0
  59. package/dist/schema.d.ts +4 -0
  60. package/dist/schema.js +10 -0
  61. package/dist/seed-scaffold.d.ts +34 -0
  62. package/dist/seed-scaffold.js +144 -0
  63. package/dist/seeder.d.ts +116 -0
  64. package/dist/seeder.js +363 -0
  65. package/dist/sql-helpers.d.ts +33 -0
  66. package/dist/sql-helpers.js +24 -0
  67. package/dist/table.d.ts +7 -0
  68. package/dist/table.js +26 -0
  69. package/dist/tools/setup.d.ts +1 -0
  70. package/dist/tools/setup.js +6 -0
  71. package/dist/transaction-context.d.ts +52 -0
  72. package/dist/transaction-context.js +62 -0
  73. package/dist/types.d.ts +151 -0
  74. package/dist/types.js +23 -0
  75. package/dist/unique-audit.d.ts +60 -0
  76. package/dist/unique-audit.js +174 -0
  77. package/dist/utils.d.ts +189 -0
  78. package/dist/utils.js +163 -0
  79. package/dist/uuid-columns.d.ts +22 -0
  80. package/dist/uuid-columns.js +68 -0
  81. package/dist/validators.d.ts +26 -0
  82. package/dist/validators.js +122 -0
  83. package/package.json +11 -11
@@ -0,0 +1,206 @@
1
+ import { log } from "@stacksjs/logging";
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
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Drivers — barrel export.
3
+ *
4
+ * Helpers shared across driver implementations live in `./helpers.ts` (NOT
5
+ * inline here) so driver modules can import them without re-importing this
6
+ * barrel and triggering a self-cycle. See `./helpers.ts` for the rationale.
7
+ */
8
+ export * from './helpers';
9
+ export * from './mysql';
10
+ export * from './postgres';
11
+ export * from './sqlite';
12
+ export { generateIndexCreationSQL } from './mysql';
13
+ export { generateIndexCreationSQL as generatePostgresIndexCreationSQL } from './postgres';
14
+ export { generateIndexCreationSQL as generateSqliteIndexCreationSQL } from './sqlite';
15
+ export * from './defaults/index';
16
+ export * from './dynamodb';
@@ -0,0 +1,9 @@
1
+ export * from "./helpers";
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";
@@ -0,0 +1,7 @@
1
+ import type { Ok } from '@stacksjs/error-handling';
2
+ export declare function resetMysqlDatabase(): Promise<Ok<string, never>>;
3
+ export declare function dropMysqlTables(): Promise<void>;
4
+ export declare function generateMysqlMigration(modelPath: string): Promise<void>;
5
+ export declare function generateMysqlTraitMigrations(): Promise<void>;
6
+ export declare function createAlterTableMigration(modelPath: string): Promise<void>;
7
+ export declare function generateIndexCreationSQL(tableName: string, index: { name: string, columns: string[], unique?: boolean, where?: string }): string;
@@ -0,0 +1,322 @@
1
+ import { log } from "@stacksjs/logging";
2
+ function italic(str) {
3
+ return `\x1B[3m${str}\x1B[23m`;
4
+ }
5
+ import { db } from "../utils";
6
+ import { createPasswordResetsTable } from "./defaults/passwords";
7
+ import { ok } from "@stacksjs/error-handling";
8
+ import { fetchOtherModelRelations, getModelName, getPivotTables, getTableName } from "@stacksjs/orm";
9
+ import { path } from "@stacksjs/path";
10
+ import { fs, globSync } from "@stacksjs/storage";
11
+ import { snakeCase } from "@stacksjs/strings";
12
+ import {
13
+ arrangeColumns,
14
+ checkPivotMigration,
15
+ deleteFrameworkModels,
16
+ deleteMigrationFiles,
17
+ fetchTables,
18
+ findDifferingKeys,
19
+ getLastMigrationFields,
20
+ getLikeableForeignKey,
21
+ getUpvoteTableName,
22
+ hasTableBeenMigrated,
23
+ isArrayEqual,
24
+ mapFieldTypeToColumnType,
25
+ pluckChanges
26
+ } from "./helpers";
27
+ import { createCategorizableTable, createCommentablesTable, createCommentUpvoteMigration, createPasskeyMigration, createQueryLogsTable, createTaggablesTable, createTaggableTable, dropCommonTables } from "./defaults/traits";
28
+ export async function resetMysqlDatabase() {
29
+ await dropMysqlTables();
30
+ await deleteFrameworkModels();
31
+ await deleteMigrationFiles();
32
+ return ok("All tables dropped successfully!");
33
+ }
34
+ export async function dropMysqlTables() {
35
+ const modelFiles = globSync([path.userModelsPath("*.ts"), path.storagePath("framework/defaults/app/Models/**/*.ts")], { absolute: !0 }), tables = await fetchTables();
36
+ for (const table of tables) {
37
+ if (!/^[a-z_][\w]*$/i.test(table))
38
+ throw Error(`[mysql] Refusing to drop table with unsafe name: ${table}`);
39
+ await db.unsafe(`DROP TABLE IF EXISTS \`${table}\``).execute();
40
+ }
41
+ await dropCommonTables();
42
+ for (const userModel of modelFiles) {
43
+ const userModelPath = (await import(userModel)).default, pivotTables = await getPivotTables(userModelPath, userModel);
44
+ for (const pivotTable of pivotTables) {
45
+ if (!/^[a-z_][\w]*$/i.test(pivotTable.table))
46
+ throw Error(`[mysql] Refusing to drop pivot table with unsafe name: ${pivotTable.table}`);
47
+ await db.unsafe(`DROP TABLE IF EXISTS \`${pivotTable.table}\``).execute();
48
+ }
49
+ }
50
+ }
51
+ export async function generateMysqlMigration(modelPath) {
52
+ 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}`);
53
+ let haveFieldsChanged = !1;
54
+ if (fs.existsSync(copiedModelPath)) {
55
+ log.info(`Fields have already been generated for ${tableName}`);
56
+ const previousFields = await getLastMigrationFields(fileName);
57
+ if (JSON.stringify(previousFields, null, 2) === fieldsString) {
58
+ log.debug(`Fields have not changed for ${tableName}`);
59
+ return;
60
+ }
61
+ haveFieldsChanged = !0;
62
+ log.debug(`Fields have changed for ${tableName}`);
63
+ } else
64
+ log.debug(`Fields have not been generated for ${tableName}`);
65
+ await Bun.$`mkdir -p ${path.frameworkPath("cache/models")} && cp ${modelPath} ${copiedModelPath}`;
66
+ const hasBeenMigrated = await hasTableBeenMigrated(tableName);
67
+ log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`);
68
+ if (haveFieldsChanged)
69
+ await createAlterTableMigration(modelPath);
70
+ else
71
+ await createTableMigration(modelPath);
72
+ }
73
+ export async function generateMysqlTraitMigrations() {
74
+ await Promise.all([
75
+ createCategorizableTable(),
76
+ createCommentablesTable(),
77
+ createTaggableTable(),
78
+ createTaggablesTable(),
79
+ createPasswordResetsTable(),
80
+ createPasskeyMigration(),
81
+ createQueryLogsTable(),
82
+ createCommentUpvoteMigration()
83
+ ]);
84
+ }
85
+ async function createTableMigration(modelPath) {
86
+ log.debug("createTableMigration modelPath:", modelPath);
87
+ const model = (await import(modelPath)).default, tableName = getTableName(model, modelPath), twoFactorEnabled = model.traits?.useAuth && typeof model.traits.useAuth !== "boolean" ? model.traits.useAuth.useTwoFactor : !1;
88
+ await createPivotTableMigration(model, modelPath);
89
+ 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;
90
+ if (useBillable && tableName === "users")
91
+ await createTableMigration(path.frameworkPath("defaults/app/Models/Subscription.ts"));
92
+ let migrationContent = `import type { Database } from '@stacksjs/database'
93
+ `;
94
+ migrationContent += `import { sql } from '@stacksjs/database'
95
+
96
+ `;
97
+ migrationContent += `export async function up(db: Database<any>) {
98
+ `;
99
+ migrationContent += ` await (db as any).schema
100
+ `;
101
+ migrationContent += ` .createTable('${tableName}')
102
+ `;
103
+ migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
104
+ `;
105
+ if (useUuid)
106
+ migrationContent += ` .addColumn('uuid', 'varchar(255)')
107
+ `;
108
+ if (useSocials) {
109
+ const socials = model.traits?.useSocials || [];
110
+ if (socials.includes("google"))
111
+ migrationContent += ` .addColumn('google_id', 'varchar(255)')
112
+ `;
113
+ if (socials.includes("github"))
114
+ migrationContent += ` .addColumn('github_id', 'varchar(255)')
115
+ `;
116
+ if (socials.includes("apple"))
117
+ migrationContent += ` .addColumn('apple_id', 'varchar(255)')
118
+ `;
119
+ if (socials.includes("twitter"))
120
+ migrationContent += ` .addColumn('twitter_id', 'varchar(255)')
121
+ `;
122
+ if (socials.includes("facebook"))
123
+ migrationContent += ` .addColumn('facebook_id', 'varchar(255)')
124
+ `;
125
+ }
126
+ for (const [fieldName, options] of arrangeColumns(model.attributes)) {
127
+ const fieldOptions = options, fieldNameFormatted = snakeCase(fieldName), columnType = mapFieldTypeToColumnType(fieldOptions.validation?.rule);
128
+ migrationContent += ` .addColumn('${fieldNameFormatted}', ${columnType}`;
129
+ const isRequired = "isRequired" in (fieldOptions.validation?.rule ?? {}) ? (fieldOptions.validation?.rule).isRequired : !1;
130
+ if (isRequired || fieldOptions.unique || fieldOptions.default !== void 0) {
131
+ migrationContent += ", col => col";
132
+ if (isRequired)
133
+ migrationContent += ".notNull()";
134
+ if (fieldOptions.unique)
135
+ migrationContent += ".unique()";
136
+ if (fieldOptions.default !== void 0)
137
+ if (typeof fieldOptions.default === "string")
138
+ migrationContent += `.defaultTo('${fieldOptions.default.replace(/'/g, "\\'")}')`;
139
+ else if (fieldOptions.default === null)
140
+ migrationContent += ".defaultTo(null)";
141
+ else
142
+ migrationContent += `.defaultTo(${fieldOptions.default})`;
143
+ migrationContent += "";
144
+ }
145
+ migrationContent += `)
146
+ `;
147
+ }
148
+ if (twoFactorEnabled !== !1 && twoFactorEnabled)
149
+ migrationContent += ` .addColumn('two_factor_secret', 'varchar(255)')
150
+ `;
151
+ if (useBillable)
152
+ migrationContent += ` .addColumn('stripe_id', 'varchar(255)')
153
+ `;
154
+ if (usePasskey)
155
+ migrationContent += ` .addColumn('public_passkey', 'varchar(255)')
156
+ `;
157
+ if (useTimestamps) {
158
+ migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
159
+ `;
160
+ migrationContent += ` .addColumn('updated_at', 'timestamp')
161
+ `;
162
+ }
163
+ if (useSoftDeletes)
164
+ migrationContent += ` .addColumn('deleted_at', 'timestamp')
165
+ `;
166
+ migrationContent += ` .execute()
167
+ `;
168
+ migrationContent += generatePrimaryKeyIndexSQL(tableName);
169
+ if (useLikeable) {
170
+ const upvoteTable = getUpvoteTableName(model, tableName);
171
+ if (upvoteTable) {
172
+ const foreignKey = getLikeableForeignKey(model, tableName);
173
+ migrationContent += `
174
+ // Create upvote table
175
+ `;
176
+ migrationContent += ` await (db as any).schema
177
+ `;
178
+ migrationContent += ` .createTable('${upvoteTable}')
179
+ `;
180
+ migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
181
+ `;
182
+ migrationContent += ` .addColumn('${foreignKey}', 'integer', col => col.notNull())
183
+ `;
184
+ migrationContent += ` .addColumn('user_id', 'integer', col => col.notNull())
185
+ `;
186
+ migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
187
+ `;
188
+ migrationContent += ` .addColumn('updated_at', 'timestamp')
189
+ `;
190
+ migrationContent += ` .execute()
191
+
192
+ `;
193
+ migrationContent += ` // Add indexes for upvote table
194
+ `;
195
+ migrationContent += ` await (db as any).schema.createIndex('${upvoteTable}_${foreignKey}_index').on('${upvoteTable}').column('${foreignKey}').execute()
196
+ `;
197
+ migrationContent += ` await (db as any).schema.createIndex('${upvoteTable}_user_${foreignKey}_unique').on('${upvoteTable}').columns(['user_id', '${foreignKey}']).unique().execute()
198
+ `;
199
+ migrationContent += ` await (db as any).schema.createIndex('${upvoteTable}_id_index').on('${upvoteTable}').column('id').execute()
200
+ `;
201
+ }
202
+ }
203
+ migrationContent += `}
204
+ `;
205
+ const migrationFileName = `${new Date().getTime().toString()}-create-${tableName}-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
206
+ log.debug(migrationFilePath);
207
+ await Bun.write(migrationFilePath, migrationContent);
208
+ log.success(`Created migration: ${italic(migrationFileName)}`);
209
+ }
210
+ async function createPivotTableMigration(model, modelPath) {
211
+ const pivotTables = await getPivotTables(model, modelPath), processedPivotTables = new Set;
212
+ if (!pivotTables.length)
213
+ return;
214
+ for (const pivotTable of pivotTables) {
215
+ if (processedPivotTables.has(pivotTable.table))
216
+ continue;
217
+ if (await checkPivotMigration(pivotTable.table)) {
218
+ processedPivotTables.add(pivotTable.table);
219
+ continue;
220
+ }
221
+ let migrationContent = `import type { Database } from '@stacksjs/database'
222
+ `;
223
+ migrationContent += `import { sql } from '@stacksjs/database'
224
+
225
+ `;
226
+ migrationContent += `export async function up(db: Database<any>) {
227
+ `;
228
+ migrationContent += ` await (db as any).schema
229
+ `;
230
+ migrationContent += ` .createTable('${pivotTable.table}')
231
+ `;
232
+ migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
233
+ `;
234
+ migrationContent += ` .addColumn('${pivotTable.firstForeignKey}', 'integer')
235
+ `;
236
+ migrationContent += ` .addColumn('${pivotTable.secondForeignKey}', 'integer')
237
+ `;
238
+ migrationContent += ` .addColumn('created_at', 'timestamp', col => col.defaultTo(sql.raw('CURRENT_TIMESTAMP')))
239
+ `;
240
+ migrationContent += ` .execute()
241
+ `;
242
+ migrationContent += ` }
243
+ `;
244
+ const migrationFileName = `${new Date().getTime().toString()}-create-${pivotTable.table}-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
245
+ await Bun.write(migrationFilePath, migrationContent);
246
+ processedPivotTables.add(pivotTable.table);
247
+ log.success(`Created pivot migration: ${migrationFileName}`);
248
+ }
249
+ }
250
+ export async function createAlterTableMigration(modelPath) {
251
+ const model = (await import(modelPath)).default, modelName = getModelName(model, modelPath), tableName = getTableName(model, modelPath);
252
+ let hasChanged = !1;
253
+ const lastFields = await getLastMigrationFields(modelName) ?? {}, currentFields = model.attributes, changes = pluckChanges(Object.keys(lastFields), Object.keys(currentFields)), fieldsToAdd = changes?.added || [], fieldsToRemove = changes?.removed || [];
254
+ let migrationContent = `import type { Database } from '@stacksjs/database'
255
+ `;
256
+ migrationContent += `import { sql } from '@stacksjs/database'
257
+
258
+ `;
259
+ migrationContent += `export async function up(db: Database<any>) {
260
+ `;
261
+ if (fieldsToAdd.length || fieldsToRemove.length) {
262
+ hasChanged = !0;
263
+ migrationContent += ` await (db as any).schema.alterTable('${tableName}')
264
+ `;
265
+ }
266
+ const fieldValidations = findDifferingKeys(lastFields, currentFields);
267
+ for (const fieldValidation of fieldValidations) {
268
+ hasChanged = !0;
269
+ const fieldNameFormatted = snakeCase(fieldValidation.key);
270
+ migrationContent += ` .modifyColumn('${fieldNameFormatted}', 'varchar(${fieldValidation.max})')
271
+ `;
272
+ }
273
+ const lastFieldOrder = Object.values(lastFields).map((attr) => attr.order), currentFieldOrder = Object.values(currentFields).map((attr) => attr.order);
274
+ if (!isArrayEqual(lastFieldOrder, currentFieldOrder)) {
275
+ hasChanged = !0;
276
+ migrationContent += reArrangeColumns(model.attributes, tableName);
277
+ }
278
+ if (hasChanged) {
279
+ migrationContent += ` .execute()
280
+ `;
281
+ migrationContent += `}
282
+ `;
283
+ const migrationFileName = `${new Date().getTime().toString()}-alter-${tableName}-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
284
+ await Bun.write(migrationFilePath, migrationContent);
285
+ log.success(`Created alter migration: ${italic(migrationFileName)}`);
286
+ }
287
+ }
288
+ export function generateIndexCreationSQL(tableName, index) {
289
+ if (index.unique || index.where) {
290
+ const unique = index.unique ? "UNIQUE " : "", cols = index.columns.map((col) => snakeCase(col)).join(", "), whereClause = index.where ? ` WHERE ${index.where}` : "";
291
+ return ` await db.unsafe(\`CREATE ${unique}INDEX IF NOT EXISTS \\\`${index.name}\\\` ON \\\`${tableName}\\\` (${cols})${whereClause}\`).execute()
292
+ `;
293
+ }
294
+ const columnsStr = index.columns.map((col) => `'${snakeCase(col)}'`).join(", ");
295
+ return ` await (db as any).schema.createIndex('${index.name}').on('${tableName}').columns([${columnsStr}]).execute()
296
+ `;
297
+ }
298
+ function generatePrimaryKeyIndexSQL(tableName) {
299
+ return ` await (db as any).schema.createIndex('${tableName}_id_index').on('${tableName}').column('id').execute()
300
+ `;
301
+ }
302
+ function generateForeignKeyIndexSQL(tableName, foreignKey) {
303
+ return ` await (db as any).schema.createIndex('${tableName}_${foreignKey}_index').on('${tableName}').column('${foreignKey}').execute()
304
+
305
+ `;
306
+ }
307
+ function reArrangeColumns(attributes, tableName) {
308
+ const fields = arrangeColumns(attributes);
309
+ let migrationContent = "", previousField = "";
310
+ for (const [fieldName] of fields) {
311
+ const fieldNameFormatted = snakeCase(fieldName);
312
+ if (previousField)
313
+ migrationContent += `await sql\`
314
+ ALTER TABLE ${tableName}
315
+ MODIFY COLUMN ${fieldNameFormatted} VARCHAR(255) NOT NULL AFTER ${snakeCase(previousField)};
316
+ \`.execute(db)
317
+
318
+ `;
319
+ previousField = fieldNameFormatted;
320
+ }
321
+ return migrationContent;
322
+ }
@@ -0,0 +1,7 @@
1
+ import type { Ok } from '@stacksjs/error-handling';
2
+ export declare function dropPostgresTables(): Promise<void>;
3
+ export declare function generatePostgresTraitMigrations(): Promise<void>;
4
+ export declare function resetPostgresDatabase(): Promise<Ok<string, never>>;
5
+ export declare function generatePostgresMigration(modelPath: string): Promise<void>;
6
+ export declare function generateIndexCreationSQL(tableName: string, index: { name: string, columns: string[], unique?: boolean, where?: string }): string;
7
+ export declare function fetchPostgresTables(): Promise<string[]>;