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.
@@ -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 sql = require("@forge/sql");
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(table) {
21
- const { columns, primaryKeys } = getTableMetadata(table);
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(table, foreignKeysSymbol, extraSymbol) {
41
+ function processForeignKeys(table2, foreignKeysSymbol, extraSymbol) {
40
42
  const foreignKeys = [];
41
43
  if (foreignKeysSymbol) {
42
- const fkArray = table[foreignKeysSymbol];
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 = table[extraSymbol];
55
+ const extraConfigBuilder = table2[extraSymbol];
54
56
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
55
- const configBuilderData = extraConfigBuilder(table);
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(table) {
73
- const symbols = Object.getOwnPropertySymbols(table);
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(table, foreignKeysSymbol, extraSymbol);
88
+ builders.foreignKeys = processForeignKeys(table2, foreignKeysSymbol, extraSymbol);
87
89
  if (extraSymbol) {
88
- const extraConfigBuilder = table[extraSymbol];
90
+ const extraConfigBuilder = table2[extraSymbol];
89
91
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
90
- const configBuilderData = extraConfigBuilder(table);
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 ? table[nameSymbol] : "",
117
- columns: columnsSymbol ? table[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((table) => {
124
- const tableMetadata = getTableMetadata(table);
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("Executing raw SQL: " + query);
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
- const droppedTables = tables.map((table) => {
656
- const metadata = getTableMetadata(table);
657
- return metadata.tableName;
658
- }).filter(Boolean);
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