forge-sql-orm 2.0.6 → 2.0.8
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 +335 -14
- package/dist/ForgeSQLORM.js +166 -39
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +150 -23
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +39 -2
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +5 -8
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- 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 +102 -3
- package/src/core/ForgeSQLQueryBuilder.ts +19 -8
- package/src/core/ForgeSQLSelectOperations.ts +10 -4
- package/src/utils/forgeDriver.ts +21 -27
- 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/dist/ForgeSQLORM.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
3
|
const drizzleOrm = require("drizzle-orm");
|
|
4
4
|
const moment = require("moment");
|
|
5
|
-
const
|
|
5
|
+
const table = require("drizzle-orm/table");
|
|
6
|
+
const sql = require("drizzle-orm/sql/sql");
|
|
7
|
+
const sql$1 = require("@forge/sql");
|
|
6
8
|
const mysql2 = require("drizzle-orm/mysql2");
|
|
7
9
|
const mysqlCore = require("drizzle-orm/mysql-core");
|
|
8
10
|
const moment$1 = require("moment/moment.js");
|
|
@@ -17,8 +19,8 @@ function extractAlias(query) {
|
|
|
17
19
|
const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
|
|
18
20
|
return match ? match[2] : query;
|
|
19
21
|
}
|
|
20
|
-
function getPrimaryKeys(
|
|
21
|
-
const { columns, primaryKeys } = getTableMetadata(
|
|
22
|
+
function getPrimaryKeys(table2) {
|
|
23
|
+
const { columns, primaryKeys } = getTableMetadata(table2);
|
|
22
24
|
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
|
|
23
25
|
if (columnPrimaryKeys.length > 0) {
|
|
24
26
|
return columnPrimaryKeys;
|
|
@@ -36,10 +38,10 @@ function getPrimaryKeys(table) {
|
|
|
36
38
|
}
|
|
37
39
|
return [];
|
|
38
40
|
}
|
|
39
|
-
function processForeignKeys(
|
|
41
|
+
function processForeignKeys(table2, foreignKeysSymbol, extraSymbol) {
|
|
40
42
|
const foreignKeys = [];
|
|
41
43
|
if (foreignKeysSymbol) {
|
|
42
|
-
const fkArray =
|
|
44
|
+
const fkArray = table2[foreignKeysSymbol];
|
|
43
45
|
if (fkArray) {
|
|
44
46
|
fkArray.forEach((fk) => {
|
|
45
47
|
if (fk.reference) {
|
|
@@ -50,9 +52,9 @@ function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
if (extraSymbol) {
|
|
53
|
-
const extraConfigBuilder =
|
|
55
|
+
const extraConfigBuilder = table2[extraSymbol];
|
|
54
56
|
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
55
|
-
const configBuilderData = extraConfigBuilder(
|
|
57
|
+
const configBuilderData = extraConfigBuilder(table2);
|
|
56
58
|
if (configBuilderData) {
|
|
57
59
|
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
58
60
|
(item) => item.value || item
|
|
@@ -69,8 +71,8 @@ function processForeignKeys(table, foreignKeysSymbol, extraSymbol) {
|
|
|
69
71
|
}
|
|
70
72
|
return foreignKeys;
|
|
71
73
|
}
|
|
72
|
-
function getTableMetadata(
|
|
73
|
-
const symbols = Object.getOwnPropertySymbols(
|
|
74
|
+
function getTableMetadata(table2) {
|
|
75
|
+
const symbols = Object.getOwnPropertySymbols(table2);
|
|
74
76
|
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
75
77
|
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
76
78
|
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("ForeignKeys)"));
|
|
@@ -83,11 +85,11 @@ function getTableMetadata(table) {
|
|
|
83
85
|
uniqueConstraints: [],
|
|
84
86
|
extras: []
|
|
85
87
|
};
|
|
86
|
-
builders.foreignKeys = processForeignKeys(
|
|
88
|
+
builders.foreignKeys = processForeignKeys(table2, foreignKeysSymbol, extraSymbol);
|
|
87
89
|
if (extraSymbol) {
|
|
88
|
-
const extraConfigBuilder =
|
|
90
|
+
const extraConfigBuilder = table2[extraSymbol];
|
|
89
91
|
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
90
|
-
const configBuilderData = extraConfigBuilder(
|
|
92
|
+
const configBuilderData = extraConfigBuilder(table2);
|
|
91
93
|
if (configBuilderData) {
|
|
92
94
|
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
93
95
|
(item) => item.value || item
|
|
@@ -113,15 +115,15 @@ function getTableMetadata(table) {
|
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
return {
|
|
116
|
-
tableName: nameSymbol ?
|
|
117
|
-
columns: columnsSymbol ?
|
|
118
|
+
tableName: nameSymbol ? table2[nameSymbol] : "",
|
|
119
|
+
columns: columnsSymbol ? table2[columnsSymbol] : {},
|
|
118
120
|
...builders
|
|
119
121
|
};
|
|
120
122
|
}
|
|
121
123
|
function generateDropTableStatements(tables) {
|
|
122
124
|
const dropStatements = [];
|
|
123
|
-
tables.forEach((
|
|
124
|
-
const tableMetadata = getTableMetadata(
|
|
125
|
+
tables.forEach((table2) => {
|
|
126
|
+
const tableMetadata = getTableMetadata(table2);
|
|
125
127
|
if (tableMetadata.tableName) {
|
|
126
128
|
dropStatements.push(`DROP TABLE IF EXISTS \`${tableMetadata.tableName}\`;`);
|
|
127
129
|
}
|
|
@@ -129,6 +131,47 @@ function generateDropTableStatements(tables) {
|
|
|
129
131
|
dropStatements.push(`DELETE FROM __migrations;`);
|
|
130
132
|
return dropStatements;
|
|
131
133
|
}
|
|
134
|
+
function mapSelectTableToAlias(table2) {
|
|
135
|
+
const { columns, tableName } = getTableMetadata(table2);
|
|
136
|
+
const selectionsTableFields = {};
|
|
137
|
+
Object.keys(columns).forEach((name) => {
|
|
138
|
+
const column = columns[name];
|
|
139
|
+
const fieldAlias = drizzleOrm.sql.raw(`${tableName}_${column.name}`);
|
|
140
|
+
selectionsTableFields[name] = drizzleOrm.sql`${column} as \`${fieldAlias}\``;
|
|
141
|
+
});
|
|
142
|
+
return selectionsTableFields;
|
|
143
|
+
}
|
|
144
|
+
function isDrizzleColumn(column) {
|
|
145
|
+
return column && typeof column === "object" && "table" in column;
|
|
146
|
+
}
|
|
147
|
+
function mapSelectAllFieldsToAlias(selections, name, fields) {
|
|
148
|
+
if (drizzleOrm.isTable(fields)) {
|
|
149
|
+
selections[name] = mapSelectTableToAlias(fields);
|
|
150
|
+
} else if (isDrizzleColumn(fields)) {
|
|
151
|
+
const column = fields;
|
|
152
|
+
let aliasName = drizzleOrm.sql.raw(`${table.getTableName(column.table)}_${column.name}`);
|
|
153
|
+
selections[name] = drizzleOrm.sql`${column} as \`${aliasName}\``;
|
|
154
|
+
} else if (sql.isSQLWrapper(fields)) {
|
|
155
|
+
selections[name] = fields;
|
|
156
|
+
} else {
|
|
157
|
+
const innerSelections = {};
|
|
158
|
+
Object.entries(fields).forEach(([iname, ifields]) => {
|
|
159
|
+
mapSelectAllFieldsToAlias(innerSelections, iname, ifields);
|
|
160
|
+
});
|
|
161
|
+
selections[name] = innerSelections;
|
|
162
|
+
}
|
|
163
|
+
return selections;
|
|
164
|
+
}
|
|
165
|
+
function mapSelectFieldsWithAlias(fields) {
|
|
166
|
+
if (!fields) {
|
|
167
|
+
throw new Error("fields is empty");
|
|
168
|
+
}
|
|
169
|
+
const selections = {};
|
|
170
|
+
Object.entries(fields).forEach(([name, fields2]) => {
|
|
171
|
+
mapSelectAllFieldsToAlias(selections, name, fields2);
|
|
172
|
+
});
|
|
173
|
+
return selections;
|
|
174
|
+
}
|
|
132
175
|
class ForgeSQLCrudOperations {
|
|
133
176
|
forgeOperations;
|
|
134
177
|
options;
|
|
@@ -450,13 +493,12 @@ class ForgeSQLSelectOperations {
|
|
|
450
493
|
*/
|
|
451
494
|
async executeRawSQL(query, params) {
|
|
452
495
|
if (this.options.logRawSqlQuery) {
|
|
453
|
-
console.debug(
|
|
496
|
+
console.debug(
|
|
497
|
+
`Executing with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
|
|
498
|
+
);
|
|
454
499
|
}
|
|
455
|
-
const sqlStatement = sql.sql.prepare(query);
|
|
500
|
+
const sqlStatement = sql$1.sql.prepare(query);
|
|
456
501
|
if (params) {
|
|
457
|
-
if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
|
|
458
|
-
console.debug("Executing with SQL Params: " + JSON.stringify(params));
|
|
459
|
-
}
|
|
460
502
|
sqlStatement.bindParams(...params);
|
|
461
503
|
}
|
|
462
504
|
const result = await sqlStatement.execute();
|
|
@@ -469,10 +511,15 @@ class ForgeSQLSelectOperations {
|
|
|
469
511
|
* @returns {Promise<UpdateQueryResponse>} The update response containing affected rows
|
|
470
512
|
*/
|
|
471
513
|
async executeRawUpdateSQL(query, params) {
|
|
472
|
-
const sqlStatement = sql.sql.prepare(query);
|
|
514
|
+
const sqlStatement = sql$1.sql.prepare(query);
|
|
473
515
|
if (params) {
|
|
474
516
|
sqlStatement.bindParams(...params);
|
|
475
517
|
}
|
|
518
|
+
if (this.options.logRawSqlQuery) {
|
|
519
|
+
console.debug(
|
|
520
|
+
`Executing Update with SQL ${query}` + params ? `, with params: ${JSON.stringify(params)}` : ""
|
|
521
|
+
);
|
|
522
|
+
}
|
|
476
523
|
const updateQueryResponseResults = await sqlStatement.execute();
|
|
477
524
|
return updateQueryResponseResults.rows;
|
|
478
525
|
}
|
|
@@ -480,20 +527,16 @@ class ForgeSQLSelectOperations {
|
|
|
480
527
|
const forgeDriver = {
|
|
481
528
|
query: async (query, params) => {
|
|
482
529
|
try {
|
|
483
|
-
const sqlStatement = await sql.sql.prepare(query.sql);
|
|
530
|
+
const sqlStatement = await sql$1.sql.prepare(query.sql);
|
|
484
531
|
if (params) {
|
|
485
532
|
await sqlStatement.bindParams(...params);
|
|
486
533
|
}
|
|
487
534
|
const result = await sqlStatement.execute();
|
|
488
535
|
let rows;
|
|
489
536
|
if (Array.isArray(result.rows)) {
|
|
490
|
-
rows = [
|
|
491
|
-
result.rows.map((r) => Object.values(r))
|
|
492
|
-
];
|
|
537
|
+
rows = [result.rows.map((r) => Object.values(r))];
|
|
493
538
|
} else {
|
|
494
|
-
rows = [
|
|
495
|
-
result.rows
|
|
496
|
-
];
|
|
539
|
+
rows = [result.rows];
|
|
497
540
|
}
|
|
498
541
|
return rows;
|
|
499
542
|
} catch (error) {
|
|
@@ -520,7 +563,7 @@ class ForgeSQLORMImpl {
|
|
|
520
563
|
if (newOptions.logRawSqlQuery) {
|
|
521
564
|
console.debug("Initializing ForgeSQLORM...");
|
|
522
565
|
}
|
|
523
|
-
this.drizzle = mysql2.drizzle(forgeDriver);
|
|
566
|
+
this.drizzle = mysql2.drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
|
|
524
567
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
525
568
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
526
569
|
} catch (error) {
|
|
@@ -565,12 +608,94 @@ class ForgeSQLORMImpl {
|
|
|
565
608
|
getDrizzleQueryBuilder() {
|
|
566
609
|
return this.drizzle;
|
|
567
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
613
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
614
|
+
*
|
|
615
|
+
* @template TSelection - The type of the selected fields
|
|
616
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
617
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
618
|
+
* @throws {Error} If fields parameter is empty
|
|
619
|
+
* @example
|
|
620
|
+
* ```typescript
|
|
621
|
+
* await forgeSQL
|
|
622
|
+
* .select({user: users, order: orders})
|
|
623
|
+
* .from(orders)
|
|
624
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
625
|
+
* ```
|
|
626
|
+
*/
|
|
627
|
+
select(fields) {
|
|
628
|
+
if (!fields) {
|
|
629
|
+
throw new Error("fields is empty");
|
|
630
|
+
}
|
|
631
|
+
return this.drizzle.select(mapSelectFieldsWithAlias(fields));
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
635
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
636
|
+
*
|
|
637
|
+
* @template TSelection - The type of the selected fields
|
|
638
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
639
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
640
|
+
* @throws {Error} If fields parameter is empty
|
|
641
|
+
* @example
|
|
642
|
+
* ```typescript
|
|
643
|
+
* await forgeSQL
|
|
644
|
+
* .selectDistinct({user: users, order: orders})
|
|
645
|
+
* .from(orders)
|
|
646
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
649
|
+
selectDistinct(fields) {
|
|
650
|
+
if (!fields) {
|
|
651
|
+
throw new Error("fields is empty");
|
|
652
|
+
}
|
|
653
|
+
return this.drizzle.selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
654
|
+
}
|
|
568
655
|
}
|
|
569
656
|
class ForgeSQLORM {
|
|
570
657
|
ormInstance;
|
|
571
658
|
constructor(options) {
|
|
572
659
|
this.ormInstance = ForgeSQLORMImpl.getInstance(options);
|
|
573
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
663
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
664
|
+
*
|
|
665
|
+
* @template TSelection - The type of the selected fields
|
|
666
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
667
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
668
|
+
* @throws {Error} If fields parameter is empty
|
|
669
|
+
* @example
|
|
670
|
+
* ```typescript
|
|
671
|
+
* await forgeSQL
|
|
672
|
+
* .select({user: users, order: orders})
|
|
673
|
+
* .from(orders)
|
|
674
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
select(fields) {
|
|
678
|
+
return this.ormInstance.getDrizzleQueryBuilder().select(mapSelectFieldsWithAlias(fields));
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
682
|
+
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
683
|
+
*
|
|
684
|
+
* @template TSelection - The type of the selected fields
|
|
685
|
+
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
686
|
+
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
687
|
+
* @throws {Error} If fields parameter is empty
|
|
688
|
+
* @example
|
|
689
|
+
* ```typescript
|
|
690
|
+
* await forgeSQL
|
|
691
|
+
* .selectDistinct({user: users, order: orders})
|
|
692
|
+
* .from(orders)
|
|
693
|
+
* .innerJoin(users, eq(orders.userId, users.id));
|
|
694
|
+
* ```
|
|
695
|
+
*/
|
|
696
|
+
selectDistinct(fields) {
|
|
697
|
+
return this.ormInstance.getDrizzleQueryBuilder().selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
698
|
+
}
|
|
574
699
|
/**
|
|
575
700
|
* Proxies the `crud` method from `ForgeSQLORMImpl`.
|
|
576
701
|
* @returns CRUD operations.
|
|
@@ -650,13 +775,12 @@ async function dropSchemaMigrations(tables) {
|
|
|
650
775
|
const dropStatements = generateDropTableStatements(tables);
|
|
651
776
|
for (const statement of dropStatements) {
|
|
652
777
|
console.warn(statement);
|
|
653
|
-
await sql.sql.executeDDL(statement);
|
|
778
|
+
await sql$1.sql.executeDDL(statement);
|
|
654
779
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
return getHttpResponse(200, "⚠️ All data in these tables has been permanently deleted. This operation cannot be undone.");
|
|
780
|
+
return getHttpResponse(
|
|
781
|
+
200,
|
|
782
|
+
"⚠️ All data in these tables has been permanently deleted. This operation cannot be undone."
|
|
783
|
+
);
|
|
660
784
|
} catch (error) {
|
|
661
785
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
662
786
|
return getHttpResponse(500, errorMessage);
|
|
@@ -664,12 +788,12 @@ async function dropSchemaMigrations(tables) {
|
|
|
664
788
|
}
|
|
665
789
|
const applySchemaMigrations = async (migration) => {
|
|
666
790
|
console.log("Provisioning the database");
|
|
667
|
-
await sql.sql._provision();
|
|
791
|
+
await sql$1.sql._provision();
|
|
668
792
|
console.info("Running schema migrations");
|
|
669
|
-
const migrations = await migration(sql.migrationRunner);
|
|
793
|
+
const migrations = await migration(sql$1.migrationRunner);
|
|
670
794
|
const successfulMigrations = await migrations.run();
|
|
671
795
|
console.info("Migrations applied:", successfulMigrations);
|
|
672
|
-
const migrationHistory = (await sql.migrationRunner.list()).map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
|
|
796
|
+
const migrationHistory = (await sql$1.migrationRunner.list()).map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
|
|
673
797
|
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
674
798
|
return {
|
|
675
799
|
headers: { "Content-Type": ["application/json"] },
|
|
@@ -707,5 +831,8 @@ exports.generateDropTableStatements = generateDropTableStatements;
|
|
|
707
831
|
exports.getHttpResponse = getHttpResponse;
|
|
708
832
|
exports.getPrimaryKeys = getPrimaryKeys;
|
|
709
833
|
exports.getTableMetadata = getTableMetadata;
|
|
834
|
+
exports.mapSelectAllFieldsToAlias = mapSelectAllFieldsToAlias;
|
|
835
|
+
exports.mapSelectFieldsWithAlias = mapSelectFieldsWithAlias;
|
|
836
|
+
exports.mapSelectTableToAlias = mapSelectTableToAlias;
|
|
710
837
|
exports.parseDateTime = parseDateTime;
|
|
711
838
|
//# sourceMappingURL=ForgeSQLORM.js.map
|