forge-sql-orm 2.0.8 → 2.0.10
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 +55 -8
- package/dist/ForgeSQLORM.js +66 -21
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +66 -21
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +4 -4
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +36 -5
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +8 -6
- package/dist/core/SystemTables.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts +5 -2
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts +23 -0
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts +20 -4
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.d.ts +28 -0
- package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -0
- package/dist/webtriggers/index.d.ts +1 -0
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist-cli/cli.js +5 -0
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs +8 -3
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/ForgeSQLORM.ts +10 -10
- package/src/core/ForgeSQLQueryBuilder.ts +36 -5
- package/src/core/SystemTables.ts +6 -3
- package/src/utils/forgeDriver.ts +28 -17
- package/src/webtriggers/applyMigrationsWebTrigger.ts +23 -0
- package/src/webtriggers/dropMigrationWebTrigger.ts +20 -4
- package/src/webtriggers/fetchSchemaWebTrigger.ts +96 -0
- package/src/webtriggers/index.ts +1 -0
package/README.md
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
- ✅ **Automatic entity generation** from MySQL/tidb databases
|
|
12
12
|
- ✅ **Automatic migration generation** from MySQL/tidb databases
|
|
13
13
|
- ✅ **Drop Migrations** Generate a migration to drop all tables and clear migrations history for subsequent schema recreation
|
|
14
|
-
- ✅ **
|
|
14
|
+
- ✅ **Schema Fetching** Development-only web trigger to retrieve current database schema and generate SQL statements for schema recreation
|
|
15
|
+
- ✅ **Ready-to-use Migration Triggers** Built-in web triggers for applying migrations, dropping tables (development-only), and fetching schema (development-only) with proper error handling and security controls
|
|
15
16
|
- ✅ **Optimistic Locking** Ensures data consistency by preventing conflicts when multiple users update the same record
|
|
16
17
|
- ✅ **Type Safety** Full TypeScript support with proper type inference
|
|
17
18
|
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
|
|
20
21
|
### 1. Direct Drizzle Usage
|
|
21
22
|
```typescript
|
|
22
|
-
import { drizzle } from "drizzle-orm/mysql-
|
|
23
|
+
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
23
24
|
import { forgeDriver } from "forge-sql-orm";
|
|
24
25
|
const db = drizzle(forgeDriver);
|
|
25
26
|
```
|
|
@@ -53,7 +54,7 @@ await forgeSQL
|
|
|
53
54
|
|
|
54
55
|
### Using Direct Drizzle
|
|
55
56
|
```typescript
|
|
56
|
-
import { drizzle } from "drizzle-orm/mysql-
|
|
57
|
+
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
57
58
|
import { forgeDriver, mapSelectFieldsWithAlias } from "forge-sql-orm";
|
|
58
59
|
|
|
59
60
|
const db = drizzle(forgeDriver);
|
|
@@ -71,6 +72,7 @@ await db
|
|
|
71
72
|
- The solution automatically creates unique aliases for each field by prefixing them with the table name
|
|
72
73
|
- This ensures that fields with the same name from different tables remain distinct in the query results
|
|
73
74
|
|
|
75
|
+
|
|
74
76
|
## Installation
|
|
75
77
|
|
|
76
78
|
Forge-SQL-ORM is designed to work with @forge/sql and requires some additional setup to ensure compatibility within Atlassian Forge.
|
|
@@ -93,7 +95,7 @@ This will:
|
|
|
93
95
|
If you prefer to use Drizzle ORM directly without the additional features of Forge-SQL-ORM (like optimistic locking), you can use the custom driver:
|
|
94
96
|
|
|
95
97
|
```typescript
|
|
96
|
-
import { drizzle } from "drizzle-orm/mysql-
|
|
98
|
+
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
97
99
|
import { forgeDriver } from "forge-sql-orm";
|
|
98
100
|
|
|
99
101
|
// Initialize drizzle with the custom driver
|
|
@@ -314,7 +316,7 @@ const forgeSQL = new ForgeSQL();
|
|
|
314
316
|
or
|
|
315
317
|
|
|
316
318
|
```typescript
|
|
317
|
-
import { drizzle } from "drizzle-orm/mysql-
|
|
319
|
+
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
318
320
|
import { forgeDriver } from "forge-sql-orm";
|
|
319
321
|
|
|
320
322
|
// Initialize drizzle with the custom driver
|
|
@@ -575,7 +577,7 @@ Commands:
|
|
|
575
577
|
|
|
576
578
|
## Web Triggers for Migrations
|
|
577
579
|
|
|
578
|
-
Forge-SQL-ORM provides
|
|
580
|
+
Forge-SQL-ORM provides web triggers for managing database migrations in Atlassian Forge:
|
|
579
581
|
|
|
580
582
|
### 1. Apply Migrations Trigger
|
|
581
583
|
|
|
@@ -647,12 +649,58 @@ Configure in `manifest.yml`:
|
|
|
647
649
|
handler: index.dropMigrations
|
|
648
650
|
```
|
|
649
651
|
|
|
652
|
+
### 3. Fetch Schema Trigger
|
|
653
|
+
|
|
654
|
+
⚠️ **DEVELOPMENT ONLY**: This trigger is designed for development environments only and should not be used in production.
|
|
655
|
+
|
|
656
|
+
This trigger retrieves the current database schema from Atlassian Forge SQL and generates SQL statements that can be used to recreate the database structure. It's useful for:
|
|
657
|
+
- Development environment setup
|
|
658
|
+
- Schema documentation
|
|
659
|
+
- Database structure verification
|
|
660
|
+
- Creating backup scripts
|
|
661
|
+
|
|
662
|
+
**Security Considerations**:
|
|
663
|
+
- This trigger exposes your database structure
|
|
664
|
+
- It temporarily disables foreign key checks
|
|
665
|
+
- It may expose sensitive table names and structures
|
|
666
|
+
- Should only be used in development environments
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
// Example usage in your Forge app
|
|
670
|
+
import { fetchSchemaWebTrigger } from "forge-sql-orm";
|
|
671
|
+
|
|
672
|
+
export const fetchSchema = async () => {
|
|
673
|
+
return fetchSchemaWebTrigger();
|
|
674
|
+
};
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
Configure in `manifest.yml`:
|
|
678
|
+
```yaml
|
|
679
|
+
webtrigger:
|
|
680
|
+
- key: fetch-schema
|
|
681
|
+
function: fetchSchema
|
|
682
|
+
sql:
|
|
683
|
+
- key: main
|
|
684
|
+
engine: mysql
|
|
685
|
+
function:
|
|
686
|
+
- key: fetchSchema
|
|
687
|
+
handler: index.fetchSchema
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
The response will contain SQL statements like:
|
|
691
|
+
```sql
|
|
692
|
+
SET foreign_key_checks = 0;
|
|
693
|
+
CREATE TABLE IF NOT EXISTS users (...);
|
|
694
|
+
CREATE TABLE IF NOT EXISTS orders (...);
|
|
695
|
+
SET foreign_key_checks = 1;
|
|
696
|
+
```
|
|
697
|
+
|
|
650
698
|
### Important Notes
|
|
651
699
|
|
|
652
700
|
**Security Considerations**:
|
|
653
701
|
- The drop migrations trigger should be restricted to development environments
|
|
702
|
+
- The fetch schema trigger should only be used in development
|
|
654
703
|
- Consider implementing additional authentication for these endpoints
|
|
655
|
-
- Use the `security` section in `manifest.yml` to control access
|
|
656
704
|
|
|
657
705
|
**Best Practices**:
|
|
658
706
|
- Always backup your data before using the drop migrations trigger
|
|
@@ -660,7 +708,6 @@ Configure in `manifest.yml`:
|
|
|
660
708
|
- Use these triggers as part of your deployment pipeline
|
|
661
709
|
- Monitor the execution logs in the Forge Developer Console
|
|
662
710
|
|
|
663
|
-
|
|
664
711
|
## License
|
|
665
712
|
This project is licensed under the **MIT License**.
|
|
666
713
|
Feel free to use it for commercial and personal projects.
|
package/dist/ForgeSQLORM.js
CHANGED
|
@@ -5,7 +5,7 @@ const moment = require("moment");
|
|
|
5
5
|
const table = require("drizzle-orm/table");
|
|
6
6
|
const sql = require("drizzle-orm/sql/sql");
|
|
7
7
|
const sql$1 = require("@forge/sql");
|
|
8
|
-
const
|
|
8
|
+
const mysqlProxy = require("drizzle-orm/mysql-proxy");
|
|
9
9
|
const mysqlCore = require("drizzle-orm/mysql-core");
|
|
10
10
|
const moment$1 = require("moment/moment.js");
|
|
11
11
|
const parseDateTime = (value, format) => {
|
|
@@ -524,25 +524,29 @@ class ForgeSQLSelectOperations {
|
|
|
524
524
|
return updateQueryResponseResults.rows;
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
|
-
const forgeDriver = {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const sqlStatement =
|
|
527
|
+
const forgeDriver = async (query, params, method) => {
|
|
528
|
+
try {
|
|
529
|
+
if (method == "execute") {
|
|
530
|
+
const sqlStatement = sql$1.sql.prepare(query);
|
|
531
|
+
if (params) {
|
|
532
|
+
sqlStatement.bindParams(...params);
|
|
533
|
+
}
|
|
534
|
+
const updateQueryResponseResults = await sqlStatement.execute();
|
|
535
|
+
let result = updateQueryResponseResults.rows;
|
|
536
|
+
return { ...result, rows: [result] };
|
|
537
|
+
} else {
|
|
538
|
+
const sqlStatement = await sql$1.sql.prepare(query);
|
|
531
539
|
if (params) {
|
|
532
540
|
await sqlStatement.bindParams(...params);
|
|
533
541
|
}
|
|
534
542
|
const result = await sqlStatement.execute();
|
|
535
543
|
let rows;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
} else {
|
|
539
|
-
rows = [result.rows];
|
|
540
|
-
}
|
|
541
|
-
return rows;
|
|
542
|
-
} catch (error) {
|
|
543
|
-
console.error("SQL Error:", JSON.stringify(error));
|
|
544
|
-
throw error;
|
|
544
|
+
rows = result.rows.map((r) => Object.values(r));
|
|
545
|
+
return { rows };
|
|
545
546
|
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error("SQL Error:", JSON.stringify(error));
|
|
549
|
+
throw error;
|
|
546
550
|
}
|
|
547
551
|
};
|
|
548
552
|
class ForgeSQLORMImpl {
|
|
@@ -563,7 +567,7 @@ class ForgeSQLORMImpl {
|
|
|
563
567
|
if (newOptions.logRawSqlQuery) {
|
|
564
568
|
console.debug("Initializing ForgeSQLORM...");
|
|
565
569
|
}
|
|
566
|
-
this.drizzle =
|
|
570
|
+
this.drizzle = mysqlProxy.drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
|
|
567
571
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
568
572
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
569
573
|
} catch (error) {
|
|
@@ -611,7 +615,7 @@ class ForgeSQLORMImpl {
|
|
|
611
615
|
/**
|
|
612
616
|
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
613
617
|
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
614
|
-
*
|
|
618
|
+
*
|
|
615
619
|
* @template TSelection - The type of the selected fields
|
|
616
620
|
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
617
621
|
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
@@ -633,7 +637,7 @@ class ForgeSQLORMImpl {
|
|
|
633
637
|
/**
|
|
634
638
|
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
635
639
|
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
636
|
-
*
|
|
640
|
+
*
|
|
637
641
|
* @template TSelection - The type of the selected fields
|
|
638
642
|
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
639
643
|
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
@@ -661,7 +665,7 @@ class ForgeSQLORM {
|
|
|
661
665
|
/**
|
|
662
666
|
* Creates a select query with unique field aliases to prevent field name collisions in joins.
|
|
663
667
|
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
664
|
-
*
|
|
668
|
+
*
|
|
665
669
|
* @template TSelection - The type of the selected fields
|
|
666
670
|
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
667
671
|
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
|
|
@@ -680,7 +684,7 @@ class ForgeSQLORM {
|
|
|
680
684
|
/**
|
|
681
685
|
* Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
|
|
682
686
|
* This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
|
|
683
|
-
*
|
|
687
|
+
*
|
|
684
688
|
* @template TSelection - The type of the selected fields
|
|
685
689
|
* @param {TSelection} fields - Object containing the fields to select, with table schemas as values
|
|
686
690
|
* @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
|
|
@@ -790,8 +794,8 @@ const applySchemaMigrations = async (migration) => {
|
|
|
790
794
|
console.log("Provisioning the database");
|
|
791
795
|
await sql$1.sql._provision();
|
|
792
796
|
console.info("Running schema migrations");
|
|
793
|
-
const
|
|
794
|
-
const successfulMigrations = await
|
|
797
|
+
const migrations2 = await migration(sql$1.migrationRunner);
|
|
798
|
+
const successfulMigrations = await migrations2.run();
|
|
795
799
|
console.info("Migrations applied:", successfulMigrations);
|
|
796
800
|
const migrationHistory = (await sql$1.migrationRunner.list()).map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`).join("\n");
|
|
797
801
|
console.info("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
@@ -802,6 +806,46 @@ const applySchemaMigrations = async (migration) => {
|
|
|
802
806
|
body: "Migrations successfully executed"
|
|
803
807
|
};
|
|
804
808
|
};
|
|
809
|
+
const migrations = mysqlCore.mysqlTable("__migrations", {
|
|
810
|
+
id: mysqlCore.bigint("id", { mode: "number" }).primaryKey().autoincrement(),
|
|
811
|
+
name: mysqlCore.varchar("name", { length: 255 }).notNull(),
|
|
812
|
+
migratedAt: mysqlCore.timestamp("migratedAt").defaultNow().notNull()
|
|
813
|
+
});
|
|
814
|
+
const forgeSystemTables = [migrations];
|
|
815
|
+
async function fetchSchemaWebTrigger() {
|
|
816
|
+
try {
|
|
817
|
+
const tables = await getTables();
|
|
818
|
+
const createTableStatements = await generateCreateTableStatements(tables);
|
|
819
|
+
const sqlStatements = wrapWithForeignKeyChecks(createTableStatements);
|
|
820
|
+
return getHttpResponse(200, sqlStatements.join(";\n"));
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.error(JSON.stringify(error));
|
|
823
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
824
|
+
return getHttpResponse(500, errorMessage);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
async function getTables() {
|
|
828
|
+
const tables = await sql$1.sql.executeDDL("SHOW TABLES");
|
|
829
|
+
return tables.rows.flatMap((tableInfo) => Object.values(tableInfo));
|
|
830
|
+
}
|
|
831
|
+
async function generateCreateTableStatements(tables) {
|
|
832
|
+
const statements = [];
|
|
833
|
+
for (const table2 of tables) {
|
|
834
|
+
const createTableResult = await sql$1.sql.executeDDL(`SHOW CREATE TABLE ${table2}`);
|
|
835
|
+
const createTableStatements = createTableResult.rows.filter((row) => !isSystemTable(row.Table)).map((row) => formatCreateTableStatement(row["Create Table"]));
|
|
836
|
+
statements.push(...createTableStatements);
|
|
837
|
+
}
|
|
838
|
+
return statements;
|
|
839
|
+
}
|
|
840
|
+
function isSystemTable(tableName) {
|
|
841
|
+
return forgeSystemTables.some((st) => table.getTableName(st) === tableName);
|
|
842
|
+
}
|
|
843
|
+
function formatCreateTableStatement(statement) {
|
|
844
|
+
return statement.replace(/"/g, "").replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
|
|
845
|
+
}
|
|
846
|
+
function wrapWithForeignKeyChecks(statements) {
|
|
847
|
+
return ["SET foreign_key_checks = 0", ...statements, "SET foreign_key_checks = 1"];
|
|
848
|
+
}
|
|
805
849
|
const getHttpResponse = (statusCode, body) => {
|
|
806
850
|
let statusText = "";
|
|
807
851
|
if (statusCode === 200) {
|
|
@@ -822,6 +866,7 @@ exports.applySchemaMigrations = applySchemaMigrations;
|
|
|
822
866
|
exports.default = ForgeSQLORM;
|
|
823
867
|
exports.dropSchemaMigrations = dropSchemaMigrations;
|
|
824
868
|
exports.extractAlias = extractAlias;
|
|
869
|
+
exports.fetchSchemaWebTrigger = fetchSchemaWebTrigger;
|
|
825
870
|
exports.forgeDateString = forgeDateString;
|
|
826
871
|
exports.forgeDateTimeString = forgeDateTimeString;
|
|
827
872
|
exports.forgeDriver = forgeDriver;
|