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.
Files changed (34) hide show
  1. package/README.md +55 -8
  2. package/dist/ForgeSQLORM.js +66 -21
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +66 -21
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLORM.d.ts +4 -4
  7. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLQueryBuilder.d.ts +36 -5
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  10. package/dist/core/SystemTables.d.ts +8 -6
  11. package/dist/core/SystemTables.d.ts.map +1 -1
  12. package/dist/utils/forgeDriver.d.ts +5 -2
  13. package/dist/utils/forgeDriver.d.ts.map +1 -1
  14. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts +23 -0
  15. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
  16. package/dist/webtriggers/dropMigrationWebTrigger.d.ts +20 -4
  17. package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
  18. package/dist/webtriggers/fetchSchemaWebTrigger.d.ts +28 -0
  19. package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -0
  20. package/dist/webtriggers/index.d.ts +1 -0
  21. package/dist/webtriggers/index.d.ts.map +1 -1
  22. package/dist-cli/cli.js +5 -0
  23. package/dist-cli/cli.js.map +1 -1
  24. package/dist-cli/cli.mjs +8 -3
  25. package/dist-cli/cli.mjs.map +1 -1
  26. package/package.json +1 -1
  27. package/src/core/ForgeSQLORM.ts +10 -10
  28. package/src/core/ForgeSQLQueryBuilder.ts +36 -5
  29. package/src/core/SystemTables.ts +6 -3
  30. package/src/utils/forgeDriver.ts +28 -17
  31. package/src/webtriggers/applyMigrationsWebTrigger.ts +23 -0
  32. package/src/webtriggers/dropMigrationWebTrigger.ts +20 -4
  33. package/src/webtriggers/fetchSchemaWebTrigger.ts +96 -0
  34. 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
- - ✅ **Ready-to-use Migration Triggers** Built-in web triggers for applying and dropping migrations with proper error handling and security controls
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-core";
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-core";
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-core";
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-core";
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 two web triggers for managing database migrations in Atlassian Forge:
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.
@@ -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 mysql2 = require("drizzle-orm/mysql2");
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
- query: async (query, params) => {
529
- try {
530
- const sqlStatement = await sql$1.sql.prepare(query.sql);
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
- if (Array.isArray(result.rows)) {
537
- rows = [result.rows.map((r) => Object.values(r))];
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 = mysql2.drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
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 migrations = await migration(sql$1.migrationRunner);
794
- const successfulMigrations = await migrations.run();
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;