forge-sql-orm 2.0.10 → 2.0.11
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/README.md +14 -37
- package/dist/ForgeSQLORM.js +119 -14
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +121 -16
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/selectAliased.d.ts +9 -0
- package/dist/lib/drizzle/extensions/selectAliased.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts +8 -4
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +10 -10
- package/src/core/ForgeSQLCrudOperations.ts +0 -425
- package/src/core/ForgeSQLORM.ts +0 -228
- package/src/core/ForgeSQLQueryBuilder.ts +0 -351
- package/src/core/ForgeSQLSelectOperations.ts +0 -93
- package/src/core/SystemTables.ts +0 -10
- package/src/index.ts +0 -10
- package/src/utils/forgeDriver.ts +0 -39
- package/src/utils/sqlUtils.ts +0 -306
- package/src/webtriggers/applyMigrationsWebTrigger.ts +0 -49
- package/src/webtriggers/dropMigrationWebTrigger.ts +0 -51
- package/src/webtriggers/fetchSchemaWebTrigger.ts +0 -96
- package/src/webtriggers/index.ts +0 -26
package/src/utils/sqlUtils.ts
DELETED
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
import moment from "moment";
|
|
2
|
-
import { AnyColumn, Column, isTable, sql } from "drizzle-orm";
|
|
3
|
-
import { AnyMySqlTable } from "drizzle-orm/mysql-core/index";
|
|
4
|
-
import { PrimaryKeyBuilder } from "drizzle-orm/mysql-core/primary-keys";
|
|
5
|
-
import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
|
|
6
|
-
import { CheckBuilder } from "drizzle-orm/mysql-core/checks";
|
|
7
|
-
import { ForeignKeyBuilder } from "drizzle-orm/mysql-core/foreign-keys";
|
|
8
|
-
import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
|
|
9
|
-
import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
|
|
10
|
-
import { MySqlTable } from "drizzle-orm/mysql-core";
|
|
11
|
-
import { getTableName } from "drizzle-orm/table";
|
|
12
|
-
import { isSQLWrapper } from "drizzle-orm/sql/sql";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Interface representing table metadata information
|
|
16
|
-
*/
|
|
17
|
-
export interface MetadataInfo {
|
|
18
|
-
/** The name of the table */
|
|
19
|
-
tableName: string;
|
|
20
|
-
/** Record of column names and their corresponding column definitions */
|
|
21
|
-
columns: Record<string, AnyColumn>;
|
|
22
|
-
/** Array of index builders */
|
|
23
|
-
indexes: AnyIndexBuilder[];
|
|
24
|
-
/** Array of check constraint builders */
|
|
25
|
-
checks: CheckBuilder[];
|
|
26
|
-
/** Array of foreign key builders */
|
|
27
|
-
foreignKeys: ForeignKeyBuilder[];
|
|
28
|
-
/** Array of primary key builders */
|
|
29
|
-
primaryKeys: PrimaryKeyBuilder[];
|
|
30
|
-
/** Array of unique constraint builders */
|
|
31
|
-
uniqueConstraints: UniqueConstraintBuilder[];
|
|
32
|
-
/** Array of all extra builders */
|
|
33
|
-
extras: any[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Interface for config builder data
|
|
38
|
-
*/
|
|
39
|
-
interface ConfigBuilderData {
|
|
40
|
-
value?: any;
|
|
41
|
-
[key: string]: any;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Parses a date string into a Date object using the specified format
|
|
46
|
-
* @param value - The date string to parse
|
|
47
|
-
* @param format - The format to use for parsing
|
|
48
|
-
* @returns Date object
|
|
49
|
-
*/
|
|
50
|
-
export const parseDateTime = (value: string, format: string): Date => {
|
|
51
|
-
const m = moment(value, format, true);
|
|
52
|
-
if (!m.isValid()) {
|
|
53
|
-
return moment(value).toDate();
|
|
54
|
-
}
|
|
55
|
-
return m.toDate();
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Extracts the alias from a SQL query
|
|
60
|
-
* @param query - The SQL query to extract the alias from
|
|
61
|
-
* @returns The extracted alias or the original query if no alias found
|
|
62
|
-
*/
|
|
63
|
-
export function extractAlias(query: string): string {
|
|
64
|
-
const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
|
|
65
|
-
return match ? match[2] : query;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Gets primary keys from the schema.
|
|
70
|
-
* @template T - The type of the table schema
|
|
71
|
-
* @param {T} table - The table schema
|
|
72
|
-
* @returns {[string, AnyColumn][]} Array of primary key name and column pairs
|
|
73
|
-
*/
|
|
74
|
-
export function getPrimaryKeys<T extends AnyMySqlTable>(table: T): [string, AnyColumn][] {
|
|
75
|
-
const { columns, primaryKeys } = getTableMetadata(table);
|
|
76
|
-
|
|
77
|
-
// First try to find primary keys in columns
|
|
78
|
-
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary) as [
|
|
79
|
-
string,
|
|
80
|
-
AnyColumn,
|
|
81
|
-
][];
|
|
82
|
-
|
|
83
|
-
if (columnPrimaryKeys.length > 0) {
|
|
84
|
-
return columnPrimaryKeys;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// If no primary keys found in columns, check primary key builders
|
|
88
|
-
if (Array.isArray(primaryKeys) && primaryKeys.length > 0) {
|
|
89
|
-
// Collect all primary key columns from all primary key builders
|
|
90
|
-
const primaryKeyColumns = new Set<[string, AnyColumn]>();
|
|
91
|
-
|
|
92
|
-
primaryKeys.forEach((primaryKeyBuilder) => {
|
|
93
|
-
// Get primary key columns from each builder
|
|
94
|
-
Object.entries(columns)
|
|
95
|
-
.filter(([, column]) => {
|
|
96
|
-
// @ts-ignore - PrimaryKeyBuilder has internal columns property
|
|
97
|
-
return primaryKeyBuilder.columns.includes(column);
|
|
98
|
-
})
|
|
99
|
-
.forEach(([name, column]) => {
|
|
100
|
-
primaryKeyColumns.add([name, column]);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return Array.from(primaryKeyColumns);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return [];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Processes foreign keys from both foreignKeysSymbol and extraSymbol
|
|
112
|
-
* @param table - The table schema
|
|
113
|
-
* @param foreignKeysSymbol - Symbol for foreign keys
|
|
114
|
-
* @param extraSymbol - Symbol for extra configuration
|
|
115
|
-
* @returns Array of foreign key builders
|
|
116
|
-
*/
|
|
117
|
-
function processForeignKeys(
|
|
118
|
-
table: AnyMySqlTable,
|
|
119
|
-
foreignKeysSymbol: symbol | undefined,
|
|
120
|
-
extraSymbol: symbol | undefined,
|
|
121
|
-
): ForeignKeyBuilder[] {
|
|
122
|
-
const foreignKeys: ForeignKeyBuilder[] = [];
|
|
123
|
-
|
|
124
|
-
// Process foreign keys from foreignKeysSymbol
|
|
125
|
-
if (foreignKeysSymbol) {
|
|
126
|
-
// @ts-ignore
|
|
127
|
-
const fkArray: any[] = table[foreignKeysSymbol];
|
|
128
|
-
if (fkArray) {
|
|
129
|
-
fkArray.forEach((fk) => {
|
|
130
|
-
if (fk.reference) {
|
|
131
|
-
const item = fk.reference(fk);
|
|
132
|
-
foreignKeys.push(item);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Process foreign keys from extraSymbol
|
|
139
|
-
if (extraSymbol) {
|
|
140
|
-
// @ts-ignore
|
|
141
|
-
const extraConfigBuilder = table[extraSymbol];
|
|
142
|
-
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
143
|
-
const configBuilderData = extraConfigBuilder(table);
|
|
144
|
-
if (configBuilderData) {
|
|
145
|
-
const configBuilders = Array.isArray(configBuilderData)
|
|
146
|
-
? configBuilderData
|
|
147
|
-
: Object.values(configBuilderData).map(
|
|
148
|
-
(item) => (item as ConfigBuilderData).value || item,
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
configBuilders.forEach((builder) => {
|
|
152
|
-
if (!builder?.constructor) return;
|
|
153
|
-
|
|
154
|
-
const builderName = builder.constructor.name.toLowerCase();
|
|
155
|
-
if (builderName.includes("foreignkeybuilder")) {
|
|
156
|
-
foreignKeys.push(builder);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return foreignKeys;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Extracts table metadata from the schema.
|
|
168
|
-
* @param {AnyMySqlTable} table - The table schema
|
|
169
|
-
* @returns {MetadataInfo} Object containing table metadata
|
|
170
|
-
*/
|
|
171
|
-
export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
|
|
172
|
-
const symbols = Object.getOwnPropertySymbols(table);
|
|
173
|
-
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
174
|
-
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
175
|
-
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys)"));
|
|
176
|
-
const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
|
|
177
|
-
|
|
178
|
-
// Initialize builders arrays
|
|
179
|
-
const builders = {
|
|
180
|
-
indexes: [] as AnyIndexBuilder[],
|
|
181
|
-
checks: [] as CheckBuilder[],
|
|
182
|
-
foreignKeys: [] as ForeignKeyBuilder[],
|
|
183
|
-
primaryKeys: [] as PrimaryKeyBuilder[],
|
|
184
|
-
uniqueConstraints: [] as UniqueConstraintBuilder[],
|
|
185
|
-
extras: [] as any[],
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// Process foreign keys
|
|
189
|
-
builders.foreignKeys = processForeignKeys(table, foreignKeysSymbol, extraSymbol);
|
|
190
|
-
|
|
191
|
-
// Process extra configuration if available
|
|
192
|
-
if (extraSymbol) {
|
|
193
|
-
// @ts-ignore
|
|
194
|
-
const extraConfigBuilder = table[extraSymbol];
|
|
195
|
-
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
196
|
-
const configBuilderData = extraConfigBuilder(table);
|
|
197
|
-
if (configBuilderData) {
|
|
198
|
-
// Convert configBuilderData to array if it's an object
|
|
199
|
-
const configBuilders = Array.isArray(configBuilderData)
|
|
200
|
-
? configBuilderData
|
|
201
|
-
: Object.values(configBuilderData).map(
|
|
202
|
-
(item) => (item as ConfigBuilderData).value || item,
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
// Process each builder
|
|
206
|
-
configBuilders.forEach((builder) => {
|
|
207
|
-
if (!builder?.constructor) return;
|
|
208
|
-
|
|
209
|
-
const builderName = builder.constructor.name.toLowerCase();
|
|
210
|
-
|
|
211
|
-
// Map builder types to their corresponding arrays
|
|
212
|
-
const builderMap = {
|
|
213
|
-
indexbuilder: builders.indexes,
|
|
214
|
-
checkbuilder: builders.checks,
|
|
215
|
-
primarykeybuilder: builders.primaryKeys,
|
|
216
|
-
uniqueconstraintbuilder: builders.uniqueConstraints,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Add builder to appropriate array if it matches any type
|
|
220
|
-
for (const [type, array] of Object.entries(builderMap)) {
|
|
221
|
-
if (builderName.includes(type)) {
|
|
222
|
-
array.push(builder);
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Always add to extras array
|
|
228
|
-
builders.extras.push(builder);
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
tableName: nameSymbol ? (table as any)[nameSymbol] : "",
|
|
236
|
-
columns: columnsSymbol ? ((table as any)[columnsSymbol] as Record<string, AnyColumn>) : {},
|
|
237
|
-
...builders,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Generates SQL statements to drop tables
|
|
243
|
-
* @param tables - Array of table schemas
|
|
244
|
-
* @returns Array of SQL statements for dropping tables
|
|
245
|
-
*/
|
|
246
|
-
export function generateDropTableStatements(tables: AnyMySqlTable[]): string[] {
|
|
247
|
-
const dropStatements: string[] = [];
|
|
248
|
-
|
|
249
|
-
tables.forEach((table) => {
|
|
250
|
-
const tableMetadata = getTableMetadata(table);
|
|
251
|
-
if (tableMetadata.tableName) {
|
|
252
|
-
dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Add statement to clear migrations table
|
|
257
|
-
dropStatements.push(`DELETE FROM __migrations;`);
|
|
258
|
-
|
|
259
|
-
return dropStatements;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function mapSelectTableToAlias(table: MySqlTable): any {
|
|
263
|
-
const { columns, tableName } = getTableMetadata(table);
|
|
264
|
-
const selectionsTableFields: Record<string, unknown> = {};
|
|
265
|
-
Object.keys(columns).forEach((name) => {
|
|
266
|
-
const column = columns[name] as AnyColumn;
|
|
267
|
-
const fieldAlias = sql.raw(`${tableName}_${column.name}`);
|
|
268
|
-
selectionsTableFields[name] = sql`${column} as \`${fieldAlias}\``;
|
|
269
|
-
});
|
|
270
|
-
return selectionsTableFields;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function isDrizzleColumn(column: any): boolean {
|
|
274
|
-
return column && typeof column === "object" && "table" in column;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export function mapSelectAllFieldsToAlias(selections: any, name: string, fields: any): any {
|
|
278
|
-
if (isTable(fields)) {
|
|
279
|
-
selections[name] = mapSelectTableToAlias(fields as MySqlTable);
|
|
280
|
-
} else if (isDrizzleColumn(fields)) {
|
|
281
|
-
const column = fields as Column;
|
|
282
|
-
let aliasName = sql.raw(`${getTableName(column.table)}_${column.name}`);
|
|
283
|
-
selections[name] = sql`${column} as \`${aliasName}\``;
|
|
284
|
-
} else if (isSQLWrapper(fields)) {
|
|
285
|
-
selections[name] = fields;
|
|
286
|
-
} else {
|
|
287
|
-
const innerSelections: any = {};
|
|
288
|
-
Object.entries(fields).forEach(([iname, ifields]) => {
|
|
289
|
-
mapSelectAllFieldsToAlias(innerSelections, iname, ifields);
|
|
290
|
-
});
|
|
291
|
-
selections[name] = innerSelections;
|
|
292
|
-
}
|
|
293
|
-
return selections;
|
|
294
|
-
}
|
|
295
|
-
export function mapSelectFieldsWithAlias<TSelection extends SelectedFields>(
|
|
296
|
-
fields: TSelection,
|
|
297
|
-
): TSelection {
|
|
298
|
-
if (!fields) {
|
|
299
|
-
throw new Error("fields is empty");
|
|
300
|
-
}
|
|
301
|
-
const selections: any = {};
|
|
302
|
-
Object.entries(fields).forEach(([name, fields]) => {
|
|
303
|
-
mapSelectAllFieldsToAlias(selections, name, fields);
|
|
304
|
-
});
|
|
305
|
-
return selections;
|
|
306
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { migrationRunner, sql } from "@forge/sql";
|
|
2
|
-
import { MigrationRunner } from "@forge/sql/out/migration";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Web trigger for applying database schema migrations in Atlassian Forge SQL.
|
|
6
|
-
* This function handles the complete migration process including:
|
|
7
|
-
* - Database provisioning
|
|
8
|
-
* - Migration execution
|
|
9
|
-
* - Migration history tracking
|
|
10
|
-
*
|
|
11
|
-
* @param migration - A function that takes a MigrationRunner instance and returns a Promise of MigrationRunner
|
|
12
|
-
* This function should define the sequence of migrations to be applied
|
|
13
|
-
* @returns {Promise<{
|
|
14
|
-
* headers: { "Content-Type": ["application/json"] },
|
|
15
|
-
* statusCode: number,
|
|
16
|
-
* statusText: string,
|
|
17
|
-
* body: string
|
|
18
|
-
* }>} A response object containing:
|
|
19
|
-
* - headers: Content-Type header set to application/json
|
|
20
|
-
* - statusCode: 200 on success
|
|
21
|
-
* - statusText: "OK" on success
|
|
22
|
-
* - body: Success message or error details
|
|
23
|
-
*
|
|
24
|
-
* @throws {Error} If database provisioning fails
|
|
25
|
-
* @throws {Error} If migration execution fails
|
|
26
|
-
*/
|
|
27
|
-
export const applySchemaMigrations = async (
|
|
28
|
-
migration: (migrationRunner: MigrationRunner) => Promise<MigrationRunner>,
|
|
29
|
-
) => {
|
|
30
|
-
console.log("Provisioning the database");
|
|
31
|
-
await sql._provision();
|
|
32
|
-
console.info("Running schema migrations");
|
|
33
|
-
const migrations = await migration(migrationRunner);
|
|
34
|
-
const successfulMigrations = await migrations.run();
|
|
35
|
-
console.info("Migrations applied:", successfulMigrations);
|
|
36
|
-
|
|
37
|
-
const migrationHistory = (await migrationRunner.list())
|
|
38
|
-
.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`)
|
|
39
|
-
.join("\n");
|
|
40
|
-
|
|
41
|
-
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
headers: { "Content-Type": ["application/json"] },
|
|
45
|
-
statusCode: 200,
|
|
46
|
-
statusText: "OK",
|
|
47
|
-
body: "Migrations successfully executed",
|
|
48
|
-
};
|
|
49
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { sql } from "@forge/sql";
|
|
2
|
-
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
3
|
-
import { generateDropTableStatements as generateStatements } from "../utils/sqlUtils";
|
|
4
|
-
import { getHttpResponse, TriggerResponse } from "./index";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* ⚠️ DEVELOPMENT ONLY WEB TRIGGER ⚠️
|
|
8
|
-
*
|
|
9
|
-
* This web trigger is designed for development environments only and will permanently delete all data in the specified tables.
|
|
10
|
-
* It generates and executes SQL statements to drop tables and their associated constraints.
|
|
11
|
-
*
|
|
12
|
-
* @warning This trigger should NEVER be used in production environments because:
|
|
13
|
-
* - It permanently deletes all data in the specified tables
|
|
14
|
-
* - The operation cannot be undone
|
|
15
|
-
* - It may affect application functionality
|
|
16
|
-
* - It could lead to data loss and system instability
|
|
17
|
-
*
|
|
18
|
-
* @param tables - Array of table schemas to drop
|
|
19
|
-
* @returns {Promise<TriggerResponse<string>>} A response containing:
|
|
20
|
-
* - On success: 200 status with warning message about permanent deletion
|
|
21
|
-
* - On failure: 500 status with error message
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* // Example usage in development only
|
|
26
|
-
* await dropSchemaMigrations([users, orders]);
|
|
27
|
-
* // ⚠️ Warning: This will permanently delete all data in users and orders tables
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export async function dropSchemaMigrations(
|
|
31
|
-
tables: AnyMySqlTable[],
|
|
32
|
-
): Promise<TriggerResponse<string>> {
|
|
33
|
-
try {
|
|
34
|
-
// Generate drop statements
|
|
35
|
-
const dropStatements = generateStatements(tables);
|
|
36
|
-
|
|
37
|
-
// Execute each statement
|
|
38
|
-
for (const statement of dropStatements) {
|
|
39
|
-
console.warn(statement);
|
|
40
|
-
await sql.executeDDL(statement);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return getHttpResponse<string>(
|
|
44
|
-
200,
|
|
45
|
-
"⚠️ All data in these tables has been permanently deleted. This operation cannot be undone.",
|
|
46
|
-
);
|
|
47
|
-
} catch (error: unknown) {
|
|
48
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
49
|
-
return getHttpResponse<string>(500, errorMessage);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { sql } from "@forge/sql";
|
|
2
|
-
import { getHttpResponse, TriggerResponse } from "./index";
|
|
3
|
-
import { forgeSystemTables } from "../core/SystemTables";
|
|
4
|
-
import { getTableName } from "drizzle-orm/table";
|
|
5
|
-
|
|
6
|
-
interface CreateTableRow {
|
|
7
|
-
Table: string;
|
|
8
|
-
"Create Table": string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* ⚠️ DEVELOPMENT ONLY WEB TRIGGER ⚠️
|
|
13
|
-
*
|
|
14
|
-
* This web trigger retrieves the current database schema from Atlassian Forge SQL.
|
|
15
|
-
* It generates SQL statements that can be used to recreate the database structure.
|
|
16
|
-
*
|
|
17
|
-
* @warning This trigger should ONLY be used in development environments. It:
|
|
18
|
-
* - Exposes your database structure
|
|
19
|
-
* - Disables foreign key checks temporarily
|
|
20
|
-
* - Generates SQL that could potentially be used maliciously
|
|
21
|
-
* - May expose sensitive table names and structures
|
|
22
|
-
*
|
|
23
|
-
* @returns {Promise<TriggerResponse<string>>} A response containing SQL statements to recreate the database schema
|
|
24
|
-
* - On success: Returns 200 status with SQL statements
|
|
25
|
-
* - On failure: Returns 500 status with error message
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* // The response will contain SQL statements like:
|
|
30
|
-
* // SET foreign_key_checks = 0;
|
|
31
|
-
* // CREATE TABLE IF NOT EXISTS users (...);
|
|
32
|
-
* // CREATE TABLE IF NOT EXISTS orders (...);
|
|
33
|
-
* // SET foreign_key_checks = 1;
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export async function fetchSchemaWebTrigger(): Promise<TriggerResponse<string>> {
|
|
37
|
-
try {
|
|
38
|
-
const tables = await getTables();
|
|
39
|
-
const createTableStatements = await generateCreateTableStatements(tables);
|
|
40
|
-
const sqlStatements = wrapWithForeignKeyChecks(createTableStatements);
|
|
41
|
-
|
|
42
|
-
return getHttpResponse<string>(200, sqlStatements.join(";\n"));
|
|
43
|
-
} catch (error: unknown) {
|
|
44
|
-
console.error(JSON.stringify(error));
|
|
45
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
46
|
-
return getHttpResponse<string>(500, errorMessage);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Retrieves all tables from the database
|
|
52
|
-
*/
|
|
53
|
-
async function getTables(): Promise<string[]> {
|
|
54
|
-
const tables = await sql.executeDDL<string>("SHOW TABLES");
|
|
55
|
-
return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Generates CREATE TABLE statements for each table
|
|
60
|
-
*/
|
|
61
|
-
async function generateCreateTableStatements(tables: string[]): Promise<string[]> {
|
|
62
|
-
const statements: string[] = [];
|
|
63
|
-
|
|
64
|
-
for (const table of tables) {
|
|
65
|
-
const createTableResult = await sql.executeDDL<CreateTableRow>(`SHOW CREATE TABLE ${table}`);
|
|
66
|
-
|
|
67
|
-
const createTableStatements = createTableResult.rows
|
|
68
|
-
.filter((row) => !isSystemTable(row.Table))
|
|
69
|
-
.map((row) => formatCreateTableStatement(row["Create Table"]));
|
|
70
|
-
|
|
71
|
-
statements.push(...createTableStatements);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return statements;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Checks if the table is a system table
|
|
79
|
-
*/
|
|
80
|
-
function isSystemTable(tableName: string): boolean {
|
|
81
|
-
return forgeSystemTables.some((st) => getTableName(st) === tableName);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Formats the CREATE TABLE statement
|
|
86
|
-
*/
|
|
87
|
-
function formatCreateTableStatement(statement: string): string {
|
|
88
|
-
return statement.replace(/"/g, "").replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Wraps the SQL statements with foreign key check controls
|
|
93
|
-
*/
|
|
94
|
-
function wrapWithForeignKeyChecks(statements: string[]): string[] {
|
|
95
|
-
return ["SET foreign_key_checks = 0", ...statements, "SET foreign_key_checks = 1"];
|
|
96
|
-
}
|
package/src/webtriggers/index.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export * from "./dropMigrationWebTrigger";
|
|
2
|
-
export * from "./applyMigrationsWebTrigger";
|
|
3
|
-
export * from "./fetchSchemaWebTrigger";
|
|
4
|
-
|
|
5
|
-
export interface TriggerResponse<BODY> {
|
|
6
|
-
body?: BODY;
|
|
7
|
-
headers?: Record<string, string[]>;
|
|
8
|
-
statusCode: number;
|
|
9
|
-
statusText?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const getHttpResponse = <Body>(statusCode: number, body: Body): TriggerResponse<Body> => {
|
|
13
|
-
let statusText = "";
|
|
14
|
-
if (statusCode === 200) {
|
|
15
|
-
statusText = "Ok";
|
|
16
|
-
} else {
|
|
17
|
-
statusText = "Bad Request";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
headers: { "Content-Type": ["application/json"] },
|
|
22
|
-
statusCode,
|
|
23
|
-
statusText,
|
|
24
|
-
body,
|
|
25
|
-
};
|
|
26
|
-
};
|