forge-sql-orm 2.0.7 → 2.0.9
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 +80 -40
- package/dist/ForgeSQLORM.js +181 -50
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +165 -34
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +40 -3
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +6 -10
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts +5 -2
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +5 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts +0 -5
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/index.d.ts +2 -2
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/ForgeSQLCrudOperations.ts +2 -2
- package/src/core/ForgeSQLORM.ts +103 -4
- package/src/core/ForgeSQLQueryBuilder.ts +20 -10
- package/src/core/ForgeSQLSelectOperations.ts +10 -4
- package/src/utils/forgeDriver.ts +34 -29
- package/src/utils/sqlUtils.ts +55 -5
- package/src/webtriggers/applyMigrationsWebTrigger.ts +7 -6
- package/src/webtriggers/dropMigrationWebTrigger.ts +8 -20
- package/src/webtriggers/index.ts +19 -20
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
SchemaSqlForgeSql,
|
|
7
7
|
} from "./ForgeSQLQueryBuilder";
|
|
8
8
|
import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
|
|
9
|
-
import { drizzle } from "drizzle-orm/
|
|
9
|
+
import { drizzle, MySqlRemoteDatabase, MySqlRemotePreparedQueryHKT } from "drizzle-orm/mysql-proxy";
|
|
10
10
|
import { forgeDriver } from "../utils/forgeDriver";
|
|
11
|
+
import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
|
|
12
|
+
import { mapSelectFieldsWithAlias } from "../utils/sqlUtils";
|
|
13
|
+
import { MySqlSelectBuilder } from "drizzle-orm/mysql-core";
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
|
|
@@ -34,7 +37,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
34
37
|
console.debug("Initializing ForgeSQLORM...");
|
|
35
38
|
}
|
|
36
39
|
// Initialize Drizzle instance with our custom driver
|
|
37
|
-
this.drizzle = drizzle(forgeDriver);
|
|
40
|
+
this.drizzle = drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
|
|
38
41
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
39
42
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
40
43
|
} catch (error) {
|
|
@@ -80,22 +83,118 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
80
83
|
*
|
|
81
84
|
* @returns A Drizzle query builder instance for query construction only.
|
|
82
85
|
*/
|
|
83
|
-
getDrizzleQueryBuilder() {
|
|
86
|
+
getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>> {
|
|
84
87
|
return this.drizzle;
|
|
85
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
92
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
93
|
+
*
|
|
94
|
+
* @template TSelection - The type of the selected fields
|
|
95
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
96
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
97
|
+
* @throws {Error} If fields parameter is empty
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* await forgeSQL
|
|
101
|
+
* .select({user: users, order: orders})
|
|
102
|
+
* .from(orders)
|
|
103
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
select<TSelection extends SelectedFields>(
|
|
107
|
+
fields: TSelection,
|
|
108
|
+
): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
|
|
109
|
+
if (!fields) {
|
|
110
|
+
throw new Error("fields is empty");
|
|
111
|
+
}
|
|
112
|
+
return this.drizzle.select(mapSelectFieldsWithAlias(fields));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
117
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
118
|
+
*
|
|
119
|
+
* @template TSelection - The type of the selected fields
|
|
120
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
121
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
122
|
+
* @throws {Error} If fields parameter is empty
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* await forgeSQL
|
|
126
|
+
* .selectDistinct({user: users, order: orders})
|
|
127
|
+
* .from(orders)
|
|
128
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
selectDistinct<TSelection extends SelectedFields>(
|
|
132
|
+
fields: TSelection,
|
|
133
|
+
): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
|
|
134
|
+
if (!fields) {
|
|
135
|
+
throw new Error("fields is empty");
|
|
136
|
+
}
|
|
137
|
+
return this.drizzle.selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
138
|
+
}
|
|
86
139
|
}
|
|
87
140
|
|
|
88
141
|
/**
|
|
89
142
|
* Public class that acts as a wrapper around the private ForgeSQLORMImpl.
|
|
90
143
|
* Provides a clean interface for working with Forge SQL and Drizzle ORM.
|
|
91
144
|
*/
|
|
92
|
-
class ForgeSQLORM {
|
|
145
|
+
class ForgeSQLORM implements ForgeSqlOperation {
|
|
93
146
|
private readonly ormInstance: ForgeSqlOperation;
|
|
94
147
|
|
|
95
148
|
constructor(options?: ForgeSqlOrmOptions) {
|
|
96
149
|
this.ormInstance = ForgeSQLORMImpl.getInstance(options);
|
|
97
150
|
}
|
|
98
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
154
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
155
|
+
*
|
|
156
|
+
* @template TSelection - The type of the selected fields
|
|
157
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
158
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
159
|
+
* @throws {Error} If fields parameter is empty
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* await forgeSQL
|
|
163
|
+
* .select({user: users, order: orders})
|
|
164
|
+
* .from(orders)
|
|
165
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
select<TSelection extends SelectedFields>(
|
|
169
|
+
fields: TSelection,
|
|
170
|
+
): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
|
|
171
|
+
return this.ormInstance.getDrizzleQueryBuilder().select(mapSelectFieldsWithAlias(fields));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
176
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
177
|
+
*
|
|
178
|
+
* @template TSelection - The type of the selected fields
|
|
179
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
180
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
181
|
+
* @throws {Error} If fields parameter is empty
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* await forgeSQL
|
|
185
|
+
* .selectDistinct({user: users, order: orders})
|
|
186
|
+
* .from(orders)
|
|
187
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
selectDistinct<TSelection extends SelectedFields>(
|
|
191
|
+
fields: TSelection,
|
|
192
|
+
): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
|
|
193
|
+
return this.ormInstance
|
|
194
|
+
.getDrizzleQueryBuilder()
|
|
195
|
+
.selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
196
|
+
}
|
|
197
|
+
|
|
99
198
|
/**
|
|
100
199
|
* Proxies the `crud` method from `ForgeSQLORMImpl`.
|
|
101
200
|
* @returns CRUD operations.
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { UpdateQueryResponse } from "@forge/sql";
|
|
2
2
|
import { SqlParameters } from "@forge/sql/out/sql-statement";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
AnyMySqlSelectQueryBuilder,
|
|
5
|
+
AnyMySqlTable,
|
|
6
|
+
customType,
|
|
7
|
+
MySqlSelectBuilder,
|
|
8
|
+
} from "drizzle-orm/mysql-core";
|
|
9
|
+
import {
|
|
10
|
+
MySqlSelectDynamic,
|
|
11
|
+
type SelectedFields,
|
|
12
|
+
} from "drizzle-orm/mysql-core/query-builders/select.types";
|
|
6
13
|
import { InferInsertModel, SQL } from "drizzle-orm";
|
|
7
14
|
import moment from "moment/moment";
|
|
8
15
|
import { parseDateTime } from "../utils/sqlUtils";
|
|
16
|
+
import { MySqlRemoteDatabase, MySqlRemotePreparedQueryHKT } from "drizzle-orm/mysql-proxy/index";
|
|
9
17
|
|
|
10
18
|
// ============= Core Types =============
|
|
11
19
|
|
|
@@ -36,7 +44,15 @@ export interface QueryBuilderForgeSql {
|
|
|
36
44
|
* Creates a new query builder for the given entity.
|
|
37
45
|
* @returns {MySql2Database<Record<string, unknown>>} The Drizzle database instance for building queries
|
|
38
46
|
*/
|
|
39
|
-
getDrizzleQueryBuilder():
|
|
47
|
+
getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>>;
|
|
48
|
+
|
|
49
|
+
select<TSelection extends SelectedFields>(
|
|
50
|
+
fields: TSelection,
|
|
51
|
+
): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
|
|
52
|
+
|
|
53
|
+
selectDistinct<TSelection extends SelectedFields>(
|
|
54
|
+
fields: TSelection,
|
|
55
|
+
): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
58
|
// ============= CRUD Operations =============
|
|
@@ -189,12 +205,6 @@ export interface ForgeSqlOrmOptions {
|
|
|
189
205
|
* @default false
|
|
190
206
|
*/
|
|
191
207
|
logRawSqlQuery?: boolean;
|
|
192
|
-
/**
|
|
193
|
-
* Enables logging of raw SQL queries in the Atlassian Forge Developer Console.
|
|
194
|
-
* Useful for debugging and monitoring SQL operations.
|
|
195
|
-
* @default false
|
|
196
|
-
*/
|
|
197
|
-
logRawSqlQueryParams?: boolean;
|
|
198
208
|
|
|
199
209
|
/**
|
|
200
210
|
* Disables optimistic locking for all operations.
|
|
@@ -57,13 +57,12 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
|
|
|
57
57
|
*/
|
|
58
58
|
async executeRawSQL<T extends object | unknown>(query: string, params?: unknown[]): Promise<T[]> {
|
|
59
59
|
if (this.options.logRawSqlQuery) {
|
|
60
|
-
console.debug(
|
|
60
|
+
console.debug(
|
|
61
|
+
`Executing with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : "",
|
|
62
|
+
);
|
|
61
63
|
}
|
|
62
64
|
const sqlStatement = sql.prepare<T>(query);
|
|
63
65
|
if (params) {
|
|
64
|
-
if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
|
|
65
|
-
console.debug("Executing with SQL Params: " + JSON.stringify(params));
|
|
66
|
-
}
|
|
67
66
|
sqlStatement.bindParams(...params);
|
|
68
67
|
}
|
|
69
68
|
const result = await sqlStatement.execute();
|
|
@@ -81,6 +80,13 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
|
|
|
81
80
|
if (params) {
|
|
82
81
|
sqlStatement.bindParams(...params);
|
|
83
82
|
}
|
|
83
|
+
if (this.options.logRawSqlQuery) {
|
|
84
|
+
console.debug(
|
|
85
|
+
`Executing Update with SQL ${query}` + params
|
|
86
|
+
? `, with params: ${JSON.stringify(params)}`
|
|
87
|
+
: "",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
84
90
|
const updateQueryResponseResults = await sqlStatement.execute();
|
|
85
91
|
return updateQueryResponseResults.rows;
|
|
86
92
|
}
|
package/src/utils/forgeDriver.ts
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
import { sql } from "@forge/sql";
|
|
2
|
-
import {AnyMySql2Connection} from "drizzle-orm/mysql2/driver";
|
|
1
|
+
import { sql, UpdateQueryResponse } from "@forge/sql";
|
|
3
2
|
|
|
4
3
|
interface ForgeSQLResult {
|
|
5
|
-
|
|
4
|
+
rows: Record<string, unknown>[] | Record<string, unknown>;
|
|
6
5
|
}
|
|
7
6
|
|
|
8
|
-
export const forgeDriver =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
7
|
+
export const forgeDriver = async (
|
|
8
|
+
query: string,
|
|
9
|
+
params: any[],
|
|
10
|
+
method: "all" | "execute",
|
|
11
|
+
): Promise<{
|
|
12
|
+
rows: any[];
|
|
13
|
+
insertId?: number;
|
|
14
|
+
affectedRows?: number;
|
|
15
|
+
}> => {
|
|
16
|
+
try {
|
|
17
|
+
if (method == "execute") {
|
|
18
|
+
const sqlStatement = sql.prepare<UpdateQueryResponse>(query);
|
|
19
|
+
if (params) {
|
|
20
|
+
sqlStatement.bindParams(...params);
|
|
21
|
+
}
|
|
22
|
+
const updateQueryResponseResults = await sqlStatement.execute();
|
|
23
|
+
let result = updateQueryResponseResults.rows as any;
|
|
24
|
+
return { ...result, rows: [result] };
|
|
25
|
+
} else {
|
|
26
|
+
const sqlStatement = await sql.prepare<unknown>(query);
|
|
27
|
+
if (params) {
|
|
28
|
+
await sqlStatement.bindParams(...params);
|
|
29
|
+
}
|
|
30
|
+
const result = (await sqlStatement.execute()) as ForgeSQLResult;
|
|
31
|
+
let rows;
|
|
32
|
+
rows = (result.rows as any[]).map((r) => Object.values(r as Record<string, unknown>));
|
|
33
|
+
return { rows: rows };
|
|
33
34
|
}
|
|
34
|
-
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("SQL Error:", JSON.stringify(error));
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
};
|
package/src/utils/sqlUtils.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import moment from "moment";
|
|
2
|
-
import { AnyColumn } from "drizzle-orm";
|
|
2
|
+
import { AnyColumn, Column, isTable, sql } from "drizzle-orm";
|
|
3
3
|
import { AnyMySqlTable } from "drizzle-orm/mysql-core/index";
|
|
4
4
|
import { PrimaryKeyBuilder } from "drizzle-orm/mysql-core/primary-keys";
|
|
5
5
|
import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
|
|
6
6
|
import { CheckBuilder } from "drizzle-orm/mysql-core/checks";
|
|
7
7
|
import { ForeignKeyBuilder } from "drizzle-orm/mysql-core/foreign-keys";
|
|
8
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";
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Interface representing table metadata information
|
|
@@ -113,7 +117,7 @@ export function getPrimaryKeys<T extends AnyMySqlTable>(table: T): [string, AnyC
|
|
|
113
117
|
function processForeignKeys(
|
|
114
118
|
table: AnyMySqlTable,
|
|
115
119
|
foreignKeysSymbol: symbol | undefined,
|
|
116
|
-
extraSymbol: symbol | undefined
|
|
120
|
+
extraSymbol: symbol | undefined,
|
|
117
121
|
): ForeignKeyBuilder[] {
|
|
118
122
|
const foreignKeys: ForeignKeyBuilder[] = [];
|
|
119
123
|
|
|
@@ -122,7 +126,7 @@ function processForeignKeys(
|
|
|
122
126
|
// @ts-ignore
|
|
123
127
|
const fkArray: any[] = table[foreignKeysSymbol];
|
|
124
128
|
if (fkArray) {
|
|
125
|
-
fkArray.forEach(fk => {
|
|
129
|
+
fkArray.forEach((fk) => {
|
|
126
130
|
if (fk.reference) {
|
|
127
131
|
const item = fk.reference(fk);
|
|
128
132
|
foreignKeys.push(item);
|
|
@@ -148,7 +152,7 @@ function processForeignKeys(
|
|
|
148
152
|
if (!builder?.constructor) return;
|
|
149
153
|
|
|
150
154
|
const builderName = builder.constructor.name.toLowerCase();
|
|
151
|
-
if (builderName.includes(
|
|
155
|
+
if (builderName.includes("foreignkeybuilder")) {
|
|
152
156
|
foreignKeys.push(builder);
|
|
153
157
|
}
|
|
154
158
|
});
|
|
@@ -242,7 +246,7 @@ export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
|
|
|
242
246
|
export function generateDropTableStatements(tables: AnyMySqlTable[]): string[] {
|
|
243
247
|
const dropStatements: string[] = [];
|
|
244
248
|
|
|
245
|
-
tables.forEach(table => {
|
|
249
|
+
tables.forEach((table) => {
|
|
246
250
|
const tableMetadata = getTableMetadata(table);
|
|
247
251
|
if (tableMetadata.tableName) {
|
|
248
252
|
dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
|
|
@@ -254,3 +258,49 @@ export function generateDropTableStatements(tables: AnyMySqlTable[]): string[] {
|
|
|
254
258
|
|
|
255
259
|
return dropStatements;
|
|
256
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,7 +1,9 @@
|
|
|
1
|
-
import {migrationRunner, sql} from "@forge/sql";
|
|
2
|
-
import {MigrationRunner} from "@forge/sql/out/migration";
|
|
1
|
+
import { migrationRunner, sql } from "@forge/sql";
|
|
2
|
+
import { MigrationRunner } from "@forge/sql/out/migration";
|
|
3
3
|
|
|
4
|
-
export const applySchemaMigrations = async (
|
|
4
|
+
export const applySchemaMigrations = async (
|
|
5
|
+
migration: (migrationRunner: MigrationRunner) => Promise<MigrationRunner>,
|
|
6
|
+
) => {
|
|
5
7
|
console.log("Provisioning the database");
|
|
6
8
|
await sql._provision();
|
|
7
9
|
console.info("Running schema migrations");
|
|
@@ -10,8 +12,8 @@ export const applySchemaMigrations = async (migration: (migrationRunner:Migratio
|
|
|
10
12
|
console.info("Migrations applied:", successfulMigrations);
|
|
11
13
|
|
|
12
14
|
const migrationHistory = (await migrationRunner.list())
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`)
|
|
16
|
+
.join("\n");
|
|
15
17
|
|
|
16
18
|
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
17
19
|
|
|
@@ -22,4 +24,3 @@ export const applySchemaMigrations = async (migration: (migrationRunner:Migratio
|
|
|
22
24
|
body: "Migrations successfully executed",
|
|
23
25
|
};
|
|
24
26
|
};
|
|
25
|
-
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { sql } from "@forge/sql";
|
|
2
2
|
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
3
|
-
import { generateDropTableStatements as generateStatements
|
|
4
|
-
import {getHttpResponse, TriggerResponse} from "./index";
|
|
5
|
-
|
|
6
|
-
export interface DropTablesResponse {
|
|
7
|
-
message: string;
|
|
8
|
-
droppedTables: string[];
|
|
9
|
-
warning?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
3
|
+
import { generateDropTableStatements as generateStatements } from "../utils/sqlUtils";
|
|
4
|
+
import { getHttpResponse, TriggerResponse } from "./index";
|
|
12
5
|
|
|
13
6
|
/**
|
|
14
7
|
* ⚠️ WARNING: This web trigger will permanently delete all data in the specified tables.
|
|
@@ -19,7 +12,7 @@ export interface DropTablesResponse {
|
|
|
19
12
|
* @returns Trigger response with execution status and list of dropped tables
|
|
20
13
|
*/
|
|
21
14
|
export async function dropSchemaMigrations(
|
|
22
|
-
tables: AnyMySqlTable[]
|
|
15
|
+
tables: AnyMySqlTable[],
|
|
23
16
|
): Promise<TriggerResponse<string>> {
|
|
24
17
|
try {
|
|
25
18
|
// Generate drop statements
|
|
@@ -31,17 +24,12 @@ export async function dropSchemaMigrations(
|
|
|
31
24
|
await sql.executeDDL(statement);
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
return metadata.tableName;
|
|
39
|
-
})
|
|
40
|
-
.filter(Boolean);
|
|
41
|
-
|
|
42
|
-
return getHttpResponse<string>(200, "⚠️ All data in these tables has been permanently deleted. This operation cannot be undone.");
|
|
27
|
+
return getHttpResponse<string>(
|
|
28
|
+
200,
|
|
29
|
+
"⚠️ All data in these tables has been permanently deleted. This operation cannot be undone.",
|
|
30
|
+
);
|
|
43
31
|
} catch (error: unknown) {
|
|
44
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
32
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
45
33
|
return getHttpResponse<string>(500, errorMessage);
|
|
46
34
|
}
|
|
47
35
|
}
|
package/src/webtriggers/index.ts
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
export * from
|
|
3
|
-
export * from './applyMigrationsWebTrigger'
|
|
1
|
+
export * from "./dropMigrationWebTrigger";
|
|
2
|
+
export * from "./applyMigrationsWebTrigger";
|
|
4
3
|
|
|
5
4
|
export interface TriggerResponse<BODY> {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
body?: BODY;
|
|
6
|
+
headers?: Record<string, string[]>;
|
|
7
|
+
statusCode: number;
|
|
8
|
+
statusText?: string;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export const getHttpResponse = <Body>(statusCode: number, body: Body): TriggerResponse<Body> => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
let statusText = "";
|
|
13
|
+
if (statusCode === 200) {
|
|
14
|
+
statusText = "Ok";
|
|
15
|
+
} else {
|
|
16
|
+
statusText = "Bad Request";
|
|
17
|
+
}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
};
|
|
19
|
+
return {
|
|
20
|
+
headers: { "Content-Type": ["application/json"] },
|
|
21
|
+
statusCode,
|
|
22
|
+
statusText,
|
|
23
|
+
body,
|
|
24
|
+
};
|
|
25
|
+
};
|